ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nc...@apache.org
Subject [08/51] [abbrv] ambari git commit: AMBARI-14416. Refactor Host Details controller
Date Wed, 23 Dec 2015 15:06:47 GMT
http://git-wip-us.apache.org/repos/asf/ambari/blob/6c38d84b/ambari-web/app/utils/configs/hosts_based_initializer_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/configs/hosts_based_initializer_mixin.js b/ambari-web/app/utils/configs/hosts_based_initializer_mixin.js
new file mode 100644
index 0000000..5c51c12
--- /dev/null
+++ b/ambari-web/app/utils/configs/hosts_based_initializer_mixin.js
@@ -0,0 +1,401 @@
+/**
+ * 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');
+
+/**
+ * Regexp for host with port ('hostName:1234')
+ *
+ * @type {string}
+ */
+var hostWithPort = "([\\w|\\.]*)(?=:)";
+
+/**
+ * Regexp for host with port and protocol ('://hostName:1234')
+ *
+ * @type {string}
+ */
+var hostWithPrefix = ":\/\/" + hostWithPort;
+
+/**
+ * Mixin describes host name computations initializers and handlers.
+ *
+ * @mixin App.HostsBasedInitializerMixin
+ */
+App.HostsBasedInitializerMixin = Em.Mixin.create({
+
+  initializerTypes: [
+    {name: 'host_with_component', method: '_initAsHostWithComponent'},
+    {name: 'hosts_with_components', method: '_initAsHostsWithComponents'},
+    {name: 'hosts_list_with_component', method: '_initAsHostsListWithComponent'}
+  ],
+
+  /**
+   * Initializer for configs with value equal to hostName with needed component
+   * Value example: 'hostName'
+   *
+   * @param {configProperty} configProperty
+   * @param {topologyLocalDB} localDB
+   * @param {object} dependencies
+   * @param {object} initializer
+   * @returns {Object}
+   * @private
+   */
+  _initAsHostWithComponent: function (configProperty, localDB, dependencies, initializer) {
+    var component = localDB.masterComponentHosts.findProperty('component', initializer.component);
+    if (!component) {
+      return configProperty;
+    }
+    if (initializer.modifier) {
+      var replaceWith = Em.getWithDefault(initializer.modifier, 'prefix', '')
+          + component.hostName
+        + Em.getWithDefault(initializer.modifier, 'suffix', '');
+      this.setRecommendedValue(configProperty, initializer.modifier.regex, replaceWith);
+    }
+    else {
+      Em.setProperties(configProperty, {
+        recommendedValue: component.hostName,
+        value: component.hostName
+      });
+    }
+
+    return configProperty;
+  },
+
+  /**
+   * Settings for <code>hosts_with_components</code>-initializer
+   * Used for configs with value equal to the hosts list
+   * May set value as array (if <code>asArray</code> is true) or as comma-sepratated string (if <code>asArray</code> is false)
+   *
+   * @see _initAsHostsWithComponents
+   * @param {string|string[]} components
+   * @param {boolean} [asArray=false]
+   * @param {boolean|undefined} [isInstalled=undefined]
+   * @returns {{type: string, components: string[], asArray: boolean}}
+   */
+  getComponentsHostsConfig: function(components, asArray, isInstalled) {
+    if (1 === arguments.length) {
+      asArray = false;
+    }
+    return {
+      type: 'hosts_with_components',
+      components: Em.makeArray(components),
+      asArray: asArray,
+      isInstalled: isInstalled
+    };
+  },
+
+  /**
+   * Initializer for configs with value equal to hostNames with needed components
+   * May be array or comma-separated list
+   * Depends on <code>initializer.asArray</code> (true - array, false - string)
+   * Value example: 'hostName1,hostName2,hostName3' or ['hostName1', 'hostName2', 'hostName3']
+   *
+   * @param {configProperty} configProperty
+   * @param {topologyLocalDB} localDB
+   * @param {object} dependencies
+   * @param {object} initializer
+   * @return {Object}
+   * @private
+   */
+  _initAsHostsWithComponents: function (configProperty, localDB, dependencies, initializer) {
+    var hostNames = localDB.masterComponentHosts.filter(function (masterComponent) {
+      var hasFound = initializer.components.contains(masterComponent.component);
+      if (Em.isNone(initializer.isInstalled)) {
+        return hasFound;
+      } else {
+        return hasFound && masterComponent.isInstalled === initializer.isInstalled;
+      }
+    }).sortProperty('hostName').mapProperty('hostName');
+    if (!initializer.asArray) {
+      hostNames = hostNames.uniq().join(',');
+    }
+    Em.setProperties(configProperty, {
+      value: hostNames,
+      recommendedValue: hostNames
+    });
+    return configProperty;
+  },
+
+  /**
+   * Settings for <code>host_with_component</code>-initializer
+   * Used for configs with value equal to hostName that has <code>component</code>
+   * Value may be modified with if <code>withModifier</code> is true (it is by default)
+   * <code>hostWithPort</code>-regexp will be used in this case
+   *
+   * @see _initAsHostWithComponent
+   * @param {string} component
+   * @param {boolean} [withModifier=true]
+   * @return {object}
+   */
+  getSimpleComponentConfig: function(component, withModifier) {
+    if (arguments.length === 1) {
+      withModifier = true;
+    }
+    var config = {
+      type: 'host_with_component',
+      component: component
+    };
+    if (withModifier) {
+      config.modifier = {
+        type: 'regexp',
+        regex: hostWithPort
+      };
+    }
+    return config;
+  },
+
+  /**
+   * Almost the same to <code>getSimpleComponentConfig</code>, but with possibility to modify <code>replaceWith</code>-value
+   * <code>prefix</code> is added before it
+   * <code>suffix</code> is added after it
+   * <code>hostWithPrefix</code>-regexp is used
+   *
+   * @see _initAsHostWithComponent
+   * @param {string} component
+   * @param {string} [prefix]
+   * @param {string} [suffix]
+   * @returns {object}
+   */
+  getComponentConfigWithAffixes: function(component, prefix, suffix) {
+    prefix = prefix || '';
+    suffix = suffix || '';
+    return {
+      type: 'host_with_component',
+      component: component,
+      modifier: {
+        type: 'regexp',
+        regex: hostWithPrefix,
+        prefix: prefix,
+        suffix: suffix
+      }
+    };
+  },
+
+  /**
+   * Settings for <code>host_with_port</code>-initializer
+   * Used for configs with value equal to hostName where some component exists concatenated with port-value
+   * Port-value is calculated according to <code>port</code> and <code>portFromDependencies</code> values
+   * If <code>portFromDependencies</code> is <code>true</code>, <code>port</code>-value is used as key of the <code>dependencies</code> (where real port-value is)
+   * Otherwise - <code>port</code>-value used as is
+   * If calculated port-value is empty, it will be skipped (and ':' too)
+   * Value also may be customized with prefix and suffix
+   *
+   * @param {string} component needed component
+   * @param {boolean} componentExists component already exists or just going to be installed
+   * @param {string} prefix=''
+   * @param {string} suffix=''
+   * @param {string} port
+   * @param {boolean} [portFromDependencies=false]
+   * @returns {{type: string, component: string, componentExists: boolean, modifier: {prefix: (string), suffix: (string)}}}
+   * @method getHostWithPortConfig
+   * @static
+   */
+  getHostWithPortConfig: function (component, componentExists, prefix, suffix, port, portFromDependencies) {
+    if (arguments.length < 6) {
+      portFromDependencies = false;
+    }
+    prefix = prefix || '';
+    suffix = suffix || '';
+    var ret = {
+      type: 'host_with_port',
+      component: component,
+      componentExists: componentExists,
+      modifier: {
+        prefix: prefix,
+        suffix: suffix
+      }
+    };
+    if (portFromDependencies) {
+      ret.portKey = port;
+    }
+    else {
+      ret.port = port;
+    }
+    return ret;
+  },
+
+  /**
+   * Initializer for configs with value equal to the hostName where some component exists
+   * Value may be customized with prefix and suffix (see <code>initializer.modifier</code>)
+   * Port-value is calculated according to <code>initializer.portKey</code> or <code>initializer.port</code> values
+   * If calculated port-value is empty, it will be skipped (and ':' too)
+   * Value-examples: 'SOME_COOL_PREFIXhost1:port1SOME_COOL_SUFFIX', 'host1:port2'
+   *
+   * @param {configProperty} configProperty
+   * @param {extendedTopologyLocalDB} localDB
+   * @param {object} dependencies
+   * @param {object} initializer
+   * @returns {object}
+   * @private
+   * @method _initAsHostWithPort
+   */
+  _initAsHostWithPort: function (configProperty, localDB, dependencies, initializer) {
+    var hostName = localDB.masterComponentHosts.filterProperty('component', initializer.component).findProperty('isInstalled', initializer.componentExists).hostName;
+    var port = this.__getPort(dependencies, initializer);
+    var value = initializer.modifier.prefix + hostName + (port ? ':' + port : '') + initializer.modifier.suffix;
+    Em.setProperties(configProperty, {
+      value: value,
+      recommendedValue: value
+    });
+    return configProperty;
+  },
+
+  /**
+   * Settings for <code>hosts_with_port</code>-initializer
+   * Used for configs with value equal to the list of hostNames with port
+   * Value also may be customized with prefix, suffix and delimiter between host:port elements
+   * Port-value is calculated according to <code>port</code> and <code>portFromDependencies</code> values
+   * If <code>portFromDependencies</code> is <code>true</code>, <code>port</code>-value is used as key of the <code>dependencies</code> (where real port-value is)
+   * Otherwise - <code>port</code>-value used as is
+   * If calculated port-value is empty, it will be skipped (and ':' too)
+   *
+   * @param {string|string[]} component hosts where this component(s) exists are used as config-value
+   * @param {string} prefix='' substring added before hosts-list
+   * @param {string} suffix='' substring added after hosts-list
+   * @param {string} delimiter=',' delimiter between hosts in the value
+   * @param {string} port if <code>portFromDependencies</code> is <code>false</code> this value is used as port for hosts
+   * if <code>portFromDependencies</code> is <code>true</code> `port` is used as key in the <code>dependencies</code> to get real port-value
+   * @param {boolean} portFromDependencies=false true - use <code>port</code> as key for <code>dependencies</code> to get real port-value,
+   * false - use <code>port</code> as port-value
+   * @returns {{type: string, component: string, modifier: {prefix: (string), suffix: (string), delimiter: (string)}}}
+   * @method getHostsWithPortConfig
+   * @static
+   */
+  getHostsWithPortConfig: function (component, prefix, suffix, delimiter, port, portFromDependencies) {
+    if (arguments.length < 6) {
+      portFromDependencies = false;
+    }
+    prefix = prefix || '';
+    suffix = suffix || '';
+    delimiter = delimiter || ',';
+    var ret = {
+      type: 'hosts_with_port',
+      component: component,
+      modifier: {
+        prefix: prefix,
+        suffix: suffix,
+        delimiter: delimiter
+      }
+    };
+    if (portFromDependencies) {
+      ret.portKey = port;
+    }
+    else {
+      ret.port = port;
+    }
+    return ret;
+  },
+
+  /**
+   * Initializer for configs with value equal to the list of hosts where some component exists
+   * Value may be customized with prefix and suffix (see <code>initializer.modifier</code>)
+   * Delimiter between hostNames also may be customized in the <code>initializer.modifier</code>
+   * Port-value is calculated according to <code>initializer.portKey</code> or <code>initializer.port</code> values
+   * If calculated port-value is empty, it will be skipped (and ':' too)
+   * Value examples: 'SOME_COOL_PREFIXhost1:port,host2:port,host2:portSOME_COOL_SUFFIX', 'host1:port|||host2:port|||host2:port'
+   *
+   * @param {configProperty} configProperty
+   * @param {topologyLocalDB} localDB
+   * @param {object} dependencies
+   * @param {object} initializer
+   * @returns {object}
+   * @private
+   * @method _initAsHostsWithPort
+   */
+  _initAsHostsWithPort: function (configProperty, localDB, dependencies, initializer) {
+    var hostNames, hosts;
+    if (Em.isArray(initializer.component)) {
+      hosts = localDB.masterComponentHosts.filter(function(masterComponent) {
+        return initializer.component.contains(masterComponent.component);
+      }).sortProperty('hostName');
+    } else {
+      hosts = localDB.masterComponentHosts.filterProperty('component', initializer.component);
+    }
+    if (Em.isNone(initializer.componentExists)) {
+      hostNames = hosts.mapProperty('hostName');
+    } else {
+      hostNames = hosts.filterProperty('isInstalled', initializer.componentExists).mapProperty('hostName');
+    }
+    var port = this.__getPort(dependencies, initializer);
+    var value = initializer.modifier.prefix + hostNames.uniq().map(function (hostName) {
+        return hostName + (port ? ':' + port : '');
+      }).join(initializer.modifier.delimiter) + initializer.modifier.suffix;
+    Em.setProperties(configProperty, {
+      value: value,
+      recommendedValue: value
+    });
+    return configProperty;
+  },
+
+  /**
+   * Returns port-value from <code>dependencies</code> accorfing to <code>initializer.portKey</code> or <code>initializer.port</code> values
+   *
+   * @param {object} dependencies
+   * @param {object} initializer
+   * @returns {string|number}
+   * @private
+   * @method __getPort
+   */
+  __getPort: function (dependencies, initializer) {
+    var portKey = initializer.portKey;
+    if (portKey) {
+      return dependencies[portKey];
+    }
+    return initializer.port;
+  },
+
+  /**
+   *
+   * @param {string} component component name
+   * @param {boolean} componentExists
+   * @returns {object}
+   */
+  getHostsListComponentConfig: function(component, componentExists, delimiter) {
+    return {
+      type: 'hosts_list_with_component',
+      component: component,
+      componentExists: componentExists,
+      modifier: {
+        delmitier: Em.isNone(delimiter) ? ',' : delimiter
+      }
+    };
+  },
+
+  /**
+   *
+   * @param {configProperty} configProperty
+   * @param {topologyLocalDB} localDB
+   * @param {object} dependencies
+   * @param {object} initializer
+   * @returns {configProperty}
+   */
+  _initAsHostsListWithComponent: function(configProperty, localDB, dependencies, initializer) {
+    var hostNames = localDB.masterComponentHosts
+        .filterProperty('component', initializer.component)
+        .filterProperty('isInstalled', initializer.componentExists)
+        .mapProperty('hostName')
+        .join(initializer.modifier.delimiter);
+
+    Em.setProperties(configProperty, {
+      value: hostNames,
+      recommendedValue: hostNames
+    });
+    return configProperty;
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/6c38d84b/ambari-web/app/utils/configs/mount_points_based_initializer_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/configs/mount_points_based_initializer_mixin.js b/ambari-web/app/utils/configs/mount_points_based_initializer_mixin.js
new file mode 100644
index 0000000..1bb9703
--- /dev/null
+++ b/ambari-web/app/utils/configs/mount_points_based_initializer_mixin.js
@@ -0,0 +1,326 @@
+/**
+ * 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');
+
+/**
+ * Regexp used to determine if mount point is windows-like
+ *
+ * @type {RegExp}
+ */
+var winRegex = /^([a-z]):\\?$/;
+
+App.MountPointsBasedInitializerMixin = Em.Mixin.create({
+
+  /**
+   * Map for methods used as value-modifiers for configProperties with values as mount point(s)
+   * Used if mount point is win-like (@see winRegex)
+   * Key: id
+   * Value: method-name
+   *
+   * @type {{default: string, file: string, slashes: string}}
+   */
+  winReplacersMap: {
+    default: '_defaultWinReplace',
+    file: '_winReplaceWithFile',
+    slashes: '_defaultWinReplaceWithAdditionalSlashes'
+  },
+
+  /**
+   * Initializer for configs with value as one of the possible mount points
+   * Only hosts that contains on the components from <code>initializer.components</code> are processed
+   * Hosts with Windows needs additional processing (@see winReplacersMap)
+   * Value example: '/', '/some/cool/dir'
+   *
+   * @param {configProperty} configProperty
+   * @param {topologyLocalDB} localDB
+   * @param {object} dependencies
+   * @param {object} initializer
+   * @return {Object}
+   */
+  _initAsSingleMountPoint: function (configProperty, localDB, dependencies, initializer) {
+    var hostsInfo = this._updateHostInfo(localDB.hosts);
+    var setOfHostNames = this._getSetOfHostNames(localDB, initializer);
+    var winReplacersMap = this.get('winReplacersMap');
+    // 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) {
+      return configProperty;
+    }
+    var allMountPoints = this._getAllMountPoints(setOfHostNames, hostsInfo);
+
+    var mPoint = allMountPoints[0].mountpoint;
+    if (mPoint === "/") {
+      mPoint = Em.get(configProperty, 'recommendedValue');
+    }
+    else {
+      var mp = mPoint.toLowerCase();
+      if (winRegex.test(mp)) {
+        var methodName = winReplacersMap[initializer.winReplacer];
+        mPoint = this[methodName].call(this, configProperty, mp);
+      }
+      else {
+        mPoint = mPoint + Em.get(configProperty, 'recommendedValue');
+      }
+    }
+    Em.setProperties(configProperty, {
+      value: mPoint,
+      recommendedValue: mPoint
+    });
+
+    return configProperty;
+  },
+
+  /**
+   * Initializer for configs with value as all of the possible mount points
+   * Only hosts that contains on the components from <code>initializer.components</code> are processed
+   * Hosts with Windows needs additional processing (@see winReplacersMap)
+   * Value example: '/\n/some/cool/dir' (`\n` - is divider)
+   *
+   * @param {Object} configProperty
+   * @param {topologyLocalDB} localDB
+   * @param {object} dependencies
+   * @param {object} initializer
+   * @return {Object}
+   */
+  _initAsMultipleMountPoints: function (configProperty, localDB, dependencies, initializer) {
+    var hostsInfo = this._updateHostInfo(localDB.hosts);
+    var self = this;
+    var setOfHostNames = this._getSetOfHostNames(localDB, initializer);
+    var winReplacersMap = this.get('winReplacersMap');
+    // 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) {
+      return configProperty;
+    }
+
+    var allMountPoints = this._getAllMountPoints(setOfHostNames, hostsInfo);
+    var mPoint = '';
+
+    allMountPoints.forEach(function (eachDrive) {
+      if (eachDrive.mountpoint === '/') {
+        mPoint += Em.get(configProperty, 'recommendedValue') + "\n";
+      }
+      else {
+        var mp = eachDrive.mountpoint.toLowerCase();
+        if (winRegex.test(mp)) {
+          var methodName = winReplacersMap[initializer.winReplacer];
+          mPoint += self[methodName].call(this, configProperty, mp);
+        }
+        else {
+          mPoint += eachDrive.mountpoint + Em.get(configProperty, 'recommendedValue') + "\n";
+        }
+      }
+    }, this);
+
+    Em.setProperties(configProperty, {
+      value: mPoint,
+      recommendedValue: mPoint
+    });
+
+    return configProperty;
+  },
+
+  /**
+   * Replace drive-based windows-path with 'file:///'
+   *
+   * @param {configProperty} configProperty
+   * @param {string} mountPoint
+   * @returns {string}
+   * @private
+   */
+  _winReplaceWithFile: function (configProperty, mountPoint) {
+    var winDriveUrl = mountPoint.toLowerCase().replace(winRegex, 'file:///$1:');
+    return winDriveUrl + Em.get(configProperty, 'recommendedValue') + '\n';
+  },
+
+  /**
+   * Replace drive-based windows-path
+   *
+   * @param {configProperty} configProperty
+   * @param {string} mountPoint
+   * @returns {string}
+   * @private
+   */
+  _defaultWinReplace: function (configProperty, mountPoint) {
+    var winDrive = mountPoint.toLowerCase().replace(winRegex, '$1:');
+    var winDir = Em.get(configProperty, 'recommendedValue').replace(/\//g, '\\');
+    return winDrive + winDir + '\n';
+  },
+
+  /**
+   * Same to <code>_defaultWinReplace</code>, but with extra-slash in the end
+   *
+   * @param {configProperty} configProperty
+   * @param {string} mountPoint
+   * @returns {string}
+   * @private
+   */
+  _defaultWinReplaceWithAdditionalSlashes: function (configProperty, mountPoint) {
+    var winDrive = mountPoint.toLowerCase().replace(winRegex, '$1:');
+    var winDir = Em.get(configProperty, 'recommendedValue').replace(/\//g, '\\\\');
+    return winDrive + winDir + '\n';
+  },
+
+  /**
+   * Update information from localDB using <code>App.Host</code>-model
+   *
+   * @param {object} hostsInfo
+   * @returns {object}
+   * @private
+   */
+  _updateHostInfo: function (hostsInfo) {
+    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
+        };
+      }
+    });
+    return hostsInfo;
+  },
+
+  /**
+   * Determines if mount point is valid
+   * Criterias:
+   * <ul>
+   *   <li>Should has available space</li>
+   *   <li>Should not be home-dir</li>
+   *   <li>Should not be docker-dir</li>
+   *   <li>Should not be boot-dir</li>
+   *   <li>Should not be dev-dir</li>
+   * </ul>
+   *
+   * @param {{mountpoint: string, available: number}} mPoint
+   * @returns {boolean} true - valid, false - invalid
+   * @private
+   */
+  _filterMountPoint: function (mPoint) {
+    var isAvailable = mPoint.available !== 0;
+    if (!isAvailable) {
+      return false;
+    }
+
+    var notHome = !['/', '/home'].contains(mPoint.mountpoint);
+    var notDocker = !['/etc/resolv.conf', '/etc/hostname', '/etc/hosts'].contains(mPoint.mountpoint);
+    var notBoot = mPoint.mountpoint && !(mPoint.mountpoint.startsWith('/boot') || mPoint.mountpoint.startsWith('/mnt'));
+    var notDev = !(['devtmpfs', 'tmpfs', 'vboxsf', 'CDFS'].contains(mPoint.type));
+
+    return notHome && notDocker && notBoot && notDev;
+  },
+
+  /**
+   * Get list of hostNames from localDB which contains needed components
+   *
+   * @param {topologyLocalDB} localDB
+   * @param {object} initializer
+   * @returns {string[]}
+   * @private
+   */
+  _getSetOfHostNames: function (localDB, initializer) {
+    var masterComponentHostsInDB = Em.getWithDefault(localDB, 'masterComponentHosts', []);
+    var slaveComponentHostsInDB = Em.getWithDefault(localDB, 'slaveComponentHosts', []);
+    var hosts = masterComponentHostsInDB.filter(function (master) {
+      return initializer.components.contains(master.component);
+    }).mapProperty('hostName');
+
+    var sHosts = slaveComponentHostsInDB.find(function (slave) {
+      return initializer.components.contains(slave.componentName);
+    });
+    if (sHosts) {
+      hosts = hosts.concat(sHosts.hosts.mapProperty('hostName'));
+    }
+    return hosts;
+  },
+
+  /**
+   * Get list of all unique valid mount points for hosts
+   *
+   * @param {string[]} setOfHostNames
+   * @param {object} hostsInfo
+   * @returns {string[]}
+   * @private
+   */
+  _getAllMountPoints: function (setOfHostNames, hostsInfo) {
+    var allMountPoints = [];
+    for (var i = 0; i < setOfHostNames.length; i++) {
+      var hostname = setOfHostNames[i];
+      var mountPointsPerHost = hostsInfo[hostname].disk_info;
+      var 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.filter(this._filterMountPoint).forEach(function (mPoint) {
+        if( !allMountPoints.findProperty("mountpoint", mPoint.mountpoint)) {
+          allMountPoints.push(mPoint);
+        }
+      }, this);
+    }
+
+    if (!allMountPoints.length) {
+      allMountPoints.push(mountPointAsRoot);
+    }
+    return allMountPoints;
+  },
+
+  /**
+   * Settings for <code>single_mountpoint</code>-initializer
+   * Used for configs with value as one of the possible mount points
+   *
+   * @see _initAsSingleMountPoint
+   * @param {string|string[]} components
+   * @param {string} winReplacer
+   * @returns {{components: string[], winReplacer: string, type: string}}
+   */
+  getSingleMountPointConfig: function (components, winReplacer) {
+    winReplacer = winReplacer || 'default';
+    return {
+      components: Em.makeArray(components),
+      winReplacer: winReplacer,
+      type: 'single_mountpoint'
+    };
+  },
+
+  /**
+   * Settings for <code>multiple_mountpoints</code>-initializer
+   * Used for configs with value as all of the possible mount points
+   *
+   * @see _initAsMultipleMountPoints
+   * @param {string|string[]} components
+   * @param {string} winReplacer
+   * @returns {{components: string[], winReplacer: string, type: string}}
+   */
+  getMultipleMountPointsConfig: function (components, winReplacer) {
+    winReplacer = winReplacer || 'default';
+    return {
+      components: Em.makeArray(components),
+      winReplacer: winReplacer,
+      type: 'multiple_mountpoints'
+    };
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/6c38d84b/ambari-web/app/utils/configs/nn_ha_config_initializer.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/configs/nn_ha_config_initializer.js b/ambari-web/app/utils/configs/nn_ha_config_initializer.js
index 80eca42..ce3e1c5 100644
--- a/ambari-web/app/utils/configs/nn_ha_config_initializer.js
+++ b/ambari-web/app/utils/configs/nn_ha_config_initializer.js
@@ -18,6 +18,7 @@
 
 var App = require('app');
 require('utils/configs/ha_config_initializer_class');
+require('utils/configs/hosts_based_initializer_mixin');
 
 /**
  * @typedef {topologyLocalDB} extendedTopologyLocalDB
@@ -80,43 +81,46 @@ function getReplaceNamespaceConfig(toReplace) {
  *
  * @class {NnHaConfigInitializer}
  */
-App.NnHaConfigInitializer = App.HaConfigInitializerClass.create({
+App.NnHaConfigInitializer = App.HaConfigInitializerClass.create(App.HostsBasedInitializerMixin, {
 
-  initializers: {
-    'dfs.ha.namenodes.${dfs.nameservices}': getRenameWithNamespaceConfig('${dfs.nameservices}'),
-    'dfs.namenode.rpc-address.${dfs.nameservices}.nn1': [
-      App.HaConfigInitializerClass.getHostWithPortConfig('NAMENODE', true, '', '', 'nnRpcPort', true),
-      getRenameWithNamespaceConfig('${dfs.nameservices}')
-    ],
-    'dfs.namenode.rpc-address.${dfs.nameservices}.nn2': [
-      App.HaConfigInitializerClass.getHostWithPortConfig('NAMENODE', false, '', '', '8020', false),
-      getRenameWithNamespaceConfig('${dfs.nameservices}')
-    ],
-    'dfs.namenode.http-address.${dfs.nameservices}.nn1': [
-      App.HaConfigInitializerClass.getHostWithPortConfig('NAMENODE', true, '', '', 'nnHttpPort', true),
-      getRenameWithNamespaceConfig('${dfs.nameservices}')
-    ],
-    'dfs.namenode.http-address.${dfs.nameservices}.nn2': [
-      App.HaConfigInitializerClass.getHostWithPortConfig('NAMENODE', false, '', '', '50070', false),
-      getRenameWithNamespaceConfig('${dfs.nameservices}')
-    ],
-    'dfs.namenode.https-address.${dfs.nameservices}.nn1': [
-      App.HaConfigInitializerClass.getHostWithPortConfig('NAMENODE', true, '', '', 'nnHttpsPort', true),
-      getRenameWithNamespaceConfig('${dfs.nameservices}')
-    ],
-    'dfs.namenode.https-address.${dfs.nameservices}.nn2': [
-      App.HaConfigInitializerClass.getHostWithPortConfig('NAMENODE', false, '', '', '50470', false),
-      getRenameWithNamespaceConfig('${dfs.nameservices}')
-    ],
-    'dfs.client.failover.proxy.provider.${dfs.nameservices}': getRenameWithNamespaceConfig('${dfs.nameservices}'),
-    'dfs.nameservices': getNamespaceConfig(),
-    'fs.defaultFS': getNamespaceConfig('hdfs://'),
-    'dfs.namenode.shared.edits.dir': [
-      App.HaConfigInitializerClass.getHostsWithPortConfig('JOURNALNODE', 'qjournal://', '/${dfs.nameservices}', ';', '8485', false),
-      getReplaceNamespaceConfig('${dfs.nameservices}')
-    ],
-    'ha.zookeeper.quorum': App.HaConfigInitializerClass.getHostsWithPortConfig('ZOOKEEPER_SERVER', '', '', ',', 'zkClientPort', true)
-  },
+  initializers: function () {
+
+    return {
+      'dfs.ha.namenodes.${dfs.nameservices}': getRenameWithNamespaceConfig('${dfs.nameservices}'),
+      'dfs.namenode.rpc-address.${dfs.nameservices}.nn1': [
+        this.getHostWithPortConfig('NAMENODE', true, '', '', 'nnRpcPort', true),
+        getRenameWithNamespaceConfig('${dfs.nameservices}')
+      ],
+      'dfs.namenode.rpc-address.${dfs.nameservices}.nn2': [
+        this.getHostWithPortConfig('NAMENODE', false, '', '', '8020', false),
+        getRenameWithNamespaceConfig('${dfs.nameservices}')
+      ],
+      'dfs.namenode.http-address.${dfs.nameservices}.nn1': [
+        this.getHostWithPortConfig('NAMENODE', true, '', '', 'nnHttpPort', true),
+        getRenameWithNamespaceConfig('${dfs.nameservices}')
+      ],
+      'dfs.namenode.http-address.${dfs.nameservices}.nn2': [
+        this.getHostWithPortConfig('NAMENODE', false, '', '', '50070', false),
+        getRenameWithNamespaceConfig('${dfs.nameservices}')
+      ],
+      'dfs.namenode.https-address.${dfs.nameservices}.nn1': [
+        this.getHostWithPortConfig('NAMENODE', true, '', '', 'nnHttpsPort', true),
+        getRenameWithNamespaceConfig('${dfs.nameservices}')
+      ],
+      'dfs.namenode.https-address.${dfs.nameservices}.nn2': [
+        this.getHostWithPortConfig('NAMENODE', false, '', '', '50470', false),
+        getRenameWithNamespaceConfig('${dfs.nameservices}')
+      ],
+      'dfs.client.failover.proxy.provider.${dfs.nameservices}': getRenameWithNamespaceConfig('${dfs.nameservices}'),
+      'dfs.nameservices': getNamespaceConfig(),
+      'fs.defaultFS': getNamespaceConfig('hdfs://'),
+      'dfs.namenode.shared.edits.dir': [
+        this.getHostsWithPortConfig('JOURNALNODE', 'qjournal://', '/${dfs.nameservices}', ';', '8485', false),
+        getReplaceNamespaceConfig('${dfs.nameservices}')
+      ],
+      'ha.zookeeper.quorum': this.getHostsWithPortConfig('ZOOKEEPER_SERVER', '', '', ',', 'zkClientPort', true)
+    };
+  }.property(),
 
   uniqueInitializers: {
     'hbase.rootdir': '_initHbaseRootDir',

http://git-wip-us.apache.org/repos/asf/ambari/blob/6c38d84b/ambari-web/app/utils/configs/rm_ha_config_initializer.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/configs/rm_ha_config_initializer.js b/ambari-web/app/utils/configs/rm_ha_config_initializer.js
index 44bd45b..0255b27 100644
--- a/ambari-web/app/utils/configs/rm_ha_config_initializer.js
+++ b/ambari-web/app/utils/configs/rm_ha_config_initializer.js
@@ -18,6 +18,7 @@
 
 var App = require('app');
 require('utils/configs/config_initializer_class');
+require('utils/configs/hosts_based_initializer_mixin');
 
 /**
  * Settings for <code>rm_hosts_with_port</code> initializer
@@ -39,24 +40,26 @@ function getRmHaHostsWithPort(port) {
  *
  * @class {RmHaConfigInitializer}
  */
-App.RmHaConfigInitializer = App.HaConfigInitializerClass.create({
+App.RmHaConfigInitializer = App.HaConfigInitializerClass.create(App.HostsBasedInitializerMixin, {
 
-  initializers: {
-    'yarn.resourcemanager.hostname.rm1': App.HaConfigInitializerClass.getHostWithPortConfig('RESOURCEMANAGER', true, '', '', ''),
-    'yarn.resourcemanager.hostname.rm2': App.HaConfigInitializerClass.getHostWithPortConfig('RESOURCEMANAGER', false,'', '', ''),
-    'yarn.resourcemanager.zk-address': App.HaConfigInitializerClass.getHostsWithPortConfig('ZOOKEEPER_SERVER', '', '', ',', 'zkClientPort', true),
-    'yarn.resourcemanager.webapp.address.rm1': App.HaConfigInitializerClass.getHostWithPortConfig('RESOURCEMANAGER', true, '', '', 'webAddressPort', true),
-    'yarn.resourcemanager.webapp.address.rm2': App.HaConfigInitializerClass.getHostWithPortConfig('RESOURCEMANAGER', false, '', '', 'webAddressPort', true),
-    'yarn.resourcemanager.webapp.https.address.rm1': App.HaConfigInitializerClass.getHostWithPortConfig('RESOURCEMANAGER', true, '', '', 'httpsWebAddressPort', true),
-    'yarn.resourcemanager.webapp.https.address.rm2': App.HaConfigInitializerClass.getHostWithPortConfig('RESOURCEMANAGER', false, '', '', 'httpsWebAddressPort', true),
-    'yarn.resourcemanager.ha': getRmHaHostsWithPort(8032),
-    'yarn.resourcemanager.scheduler.ha': getRmHaHostsWithPort(8030)
-  },
+  initializers: function () {
+    return {
+      'yarn.resourcemanager.hostname.rm1': this.getHostWithPortConfig('RESOURCEMANAGER', true, '', '', ''),
+      'yarn.resourcemanager.hostname.rm2': this.getHostWithPortConfig('RESOURCEMANAGER', false,'', '', ''),
+      'yarn.resourcemanager.zk-address': this.getHostsWithPortConfig('ZOOKEEPER_SERVER', '', '', ',', 'zkClientPort', true),
+      'yarn.resourcemanager.webapp.address.rm1': this.getHostWithPortConfig('RESOURCEMANAGER', true, '', '', 'webAddressPort', true),
+      'yarn.resourcemanager.webapp.address.rm2': this.getHostWithPortConfig('RESOURCEMANAGER', false, '', '', 'webAddressPort', true),
+      'yarn.resourcemanager.webapp.https.address.rm1': this.getHostWithPortConfig('RESOURCEMANAGER', true, '', '', 'httpsWebAddressPort', true),
+      'yarn.resourcemanager.webapp.https.address.rm2': this.getHostWithPortConfig('RESOURCEMANAGER', false, '', '', 'httpsWebAddressPort', true),
+      'yarn.resourcemanager.ha': getRmHaHostsWithPort(8032),
+      'yarn.resourcemanager.scheduler.ha': getRmHaHostsWithPort(8030)
+    };
+  }.property(),
 
   initializerTypes: [
     {name: 'rm_hosts_with_port', method: '_initRmHaHostsWithPort'},
   ],
-  
+
   /**
    * Initializer for configs that should be updated with yarn resourcemanager ha host addresses with port
    *
@@ -81,4 +84,4 @@ App.RmHaConfigInitializer = App.HaConfigInitializerClass.create({
    return configProperty;
   }
 
-});
\ No newline at end of file
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/6c38d84b/ambari-web/test/controllers/main/host/details_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/host/details_test.js b/ambari-web/test/controllers/main/host/details_test.js
index 9c8e4ad..947ec82 100644
--- a/ambari-web/test/controllers/main/host/details_test.js
+++ b/ambari-web/test/controllers/main/host/details_test.js
@@ -33,7 +33,6 @@ function getController() {
     })
   });
 }
-
 describe('App.MainHostDetailsController', function () {
 
   beforeEach(function () {
@@ -1044,15 +1043,9 @@ describe('App.MainHostDetailsController', function () {
       };
 
     beforeEach(function () {
-      sinon.stub(controller, "getZkServerHosts", Em.K);
-      sinon.stub(controller, "concatZkNames", Em.K);
-      sinon.stub(controller, "setZKConfigs", Em.K);
       sinon.stub(controller, 'saveConfigsBatch', Em.K);
     });
     afterEach(function () {
-      controller.getZkServerHosts.restore();
-      controller.concatZkNames.restore();
-      controller.setZKConfigs.restore();
       controller.saveConfigsBatch.restore();
     });
 
@@ -1097,203 +1090,281 @@ describe('App.MainHostDetailsController', function () {
     });
   });
 
-  describe('#setZKConfigs()', function () {
-    it('configs is null', function () {
-      expect(controller.setZKConfigs(null)).to.be.false;
-    });
-    it('zks is null', function () {
-      expect(controller.setZKConfigs({}, '', null)).to.be.false;
-    });
-    it('isHaEnabled = true', function () {
-      var configs = {'core-site': {}};
-      App.HostComponent.find().clear();
-      App.store.load(App.Service, {
-        id: 'HDFS',
-        service_name: 'HDFS'
+  describe('#updateZkConfigs()', function () {
+    var makeHostComponentModel = function(componentName, hostNames) {
+      return hostNames.map(function(hostName) {
+        return {
+          componentName: componentName,
+          hostName: hostName
+        };
       });
-      App.propertyDidChange('isHaEnabled');
-      expect(controller.setZKConfigs(configs, 'host1:2181', [])).to.be.true;
-      expect(configs).to.eql({
-        "core-site": {
-          "ha.zookeeper.quorum": "host1:2181"
+    };
+
+    var tests = [
+      {
+        appGetterStubs: {
+          isHaEnabled: true
+        },
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "core-site": {
+            "ha.zookeeper.quorum": "host2:8080"
+          }
+        },
+        m: 'NameNode HA enabled, ha.zookeeper.quorum config should be updated',
+        e: {
+          configs: {
+            "core-site": {
+              "ha.zookeeper.quorum": "host1:2181,host2:2181"
+            }
+          }
         }
-      });
-      App.store.load(App.HostComponent, {
-        id: 'SECONDARY_NAMENODE_host1',
-        component_name: 'SECONDARY_NAMENODE'
-      });
-      App.propertyDidChange('isHaEnabled');
-    });
-    it('hbase-site is present', function () {
-      var configs = {'hbase-site': {}};
-      expect(controller.setZKConfigs(configs, '', ['host1', 'host2'])).to.be.true;
-      expect(configs).to.eql({
-        "hbase-site": {
-          "hbase.zookeeper.quorum": "host1,host2"
+      },
+      {
+        appGetterStubs: {
+          isHaEnabled: false
+        },
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "core-site": {
+            "ha.zookeeper.quorum": "host3:8080"
+          }
+        },
+        m: 'NameNode HA disabled, ha.zookeeper.quorum config should be untouched',
+        e: {
+          configs: {
+            "core-site": {
+              "ha.zookeeper.quorum": "host3:8080"
+            }
+          }
         }
-      });
-    });
-    it('accumulo-site is present', function () {
-      var configs = {'accumulo-site': {}};
-      expect(controller.setZKConfigs(configs, 'host1:2181', [])).to.be.true;
-      expect(configs).to.eql({
-        "accumulo-site": {
-          "instance.zookeeper.host": 'host1:2181'
+      },
+      {
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "hbase-site": {
+            "hbase.zookeeper.quorum": "host3"
+          }
+        },
+        m: 'hbase.zookeeper.quorum property update test',
+        e: {
+          configs: {
+            "hbase-site": {
+              "hbase.zookeeper.quorum": "host1,host2"
+            }
+          }
         }
-      });
-    });
-    it('webhcat-site is present', function () {
-      var configs = {'webhcat-site': {}};
-      expect(controller.setZKConfigs(configs, 'host1:2181', [])).to.be.true;
-      expect(configs).to.eql({
-        "webhcat-site": {
-          "templeton.zookeeper.hosts": "host1:2181"
+      },
+      {
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        ctrlStubs: {
+          'content.hostName': 'host2',
+          fromDeleteHost: true
+        },
+        configs: {
+          "zoo.cfg": {
+            "clientPort": "1919"
+          },
+          "accumulo-site": {
+            "instance.zookeeper.host": "host3:2020"
+          }
+        },
+        m: 'instance.zookeeper.host property update test, zookeper marked to delete from host2',
+        e: {
+          configs: {
+            "zoo.cfg": {
+              "clientPort": "1919"
+            },
+            "accumulo-site": {
+              "instance.zookeeper.host": "host1:1919"
+            }
+          }
         }
-      });
-    });
-    it('hive-site is present and stack < 2.2', function () {
-      var version = App.get('currentStackVersion');
-      var configs = {'hive-site': {}};
-      App.set('currentStackVersion', 'HDP-2.1.0');
-      expect(controller.setZKConfigs(configs, 'host1:2181', [])).to.be.true;
-      expect(configs).to.eql({
-        "hive-site": {
-          'hive.cluster.delegation.token.store.zookeeper.connectString': "host1:2181"
+      },
+      {
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "webhcat-site": {
+            "templeton.zookeeper.hosts": "host3:2020"
+          }
+        },
+        m: 'templeton.zookeeper.hosts property update test',
+        e: {
+          configs: {
+            "webhcat-site": {
+              "templeton.zookeeper.hosts": "host1:2181,host2:2181"
+            }
+          }
         }
-      });
-      App.set('currentStackVersion', version);
-    });
-    it('hive-site is present and stack > 2.2', function () {
-      var version = App.get('currentStackVersion');
-      var configs = {'hive-site': {}};
-      App.set('currentStackVersion', 'HDP-2.2.0');
-      expect(controller.setZKConfigs(configs, 'host1:2181', [])).to.be.true;
-      expect(configs).to.eql({
-        "hive-site": {
-          'hive.cluster.delegation.token.store.zookeeper.connectString': "host1:2181",
-          'hive.zookeeper.quorum': "host1:2181"
+      },
+      {
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "hive-site": {
+            "hive.cluster.delegation.token.store.zookeeper.connectString": "host3:2020"
+          }
+        },
+        m: 'hive.cluster.delegation.token.store.zookeeper.connectString property update test',
+        e: {
+          configs: {
+            "hive-site": {
+              "hive.cluster.delegation.token.store.zookeeper.connectString": "host1:2181,host2:2181"
+            }
+          }
         }
-      });
-      App.set('currentStackVersion', version);
-    });
-    it('yarn-site is present and stack > 2.2', function () {
-      var version = App.get('currentStackVersion');
-      var configs = {'yarn-site': {}};
-      App.set('currentStackVersion', 'HDP-2.2.0');
-      expect(controller.setZKConfigs(configs, 'host1:2181', [])).to.be.true;
-      expect(configs).to.eql({
-        "yarn-site": {
-          'hadoop.registry.zk.quorum': "host1:2181",
-          'yarn.resourcemanager.zk-address': "host1:2181"
+      },
+      {
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "storm-site": {
+            "storm.zookeeper.servers": "['host3','host2']"
+          }
+        },
+        m: 'storm.zookeeper.servers property update test',
+        e: {
+          configs: {
+            "storm-site": {
+              "storm.zookeeper.servers": "['host1','host2']"
+            }
+          }
         }
-      });
-      App.set('currentStackVersion', version);
-    });
-    it('storm-site is present', function () {
-      var configs = {'storm-site': {}};
-      expect(controller.setZKConfigs(configs, '', ["host1", 'host2'])).to.be.true;
-      expect(configs).to.eql({
-        "storm-site": {
-          "storm.zookeeper.servers": "['host1','host2']"
+      },
+      {
+        appGetterStubs: {
+          isRMHaEnabled: true
+        },
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "yarn-site": {
+            "yarn.resourcemanager.zk-address": "host3:2181"
+          }
+        },
+        m: 'yarn.resourcemanager.zk-address property, ResourceManager HA enabled. Property value should be changed.',
+        e: {
+          configs: {
+            "yarn-site": {
+              "yarn.resourcemanager.zk-address": "host1:2181,host2:2181"
+            }
+          }
         }
-      });
-    });
-    it('isRMHaEnabled true', function () {
-      var configs = {'yarn-site': {}};
-      sinon.stub(App, 'get').withArgs('isRMHaEnabled').returns(true);
-      expect(controller.setZKConfigs(configs, 'host1:2181', ['host1', 'host2'])).to.be.true;
-      expect(configs).to.eql({
-        "yarn-site": {
-          "yarn.resourcemanager.zk-address": "host1:2181"
+      },
+      {
+        appGetterStubs: {
+          isRMHaEnabled: false
+        },
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "yarn-site": {
+            "yarn.resourcemanager.zk-address": "host3:2181"
+          }
+        },
+        m: 'yarn.resourcemanager.zk-address property, ResourceManager HA not activated. Property value should be untouched.',
+        e: {
+          configs: {
+            "yarn-site": {
+              "yarn.resourcemanager.zk-address": "host3:2181"
+            }
+          }
         }
-      });
-      App.get.restore();
-    });
-  });
-
-  describe('#concatZkNames()', function () {
-    it('No ZooKeeper hosts', function () {
-      expect(controller.concatZkNames([])).to.equal('');
-    });
-    it('One ZooKeeper host', function () {
-      expect(controller.concatZkNames(['host1'], '2181')).to.equal('host1:2181');
-    });
-    it('Two ZooKeeper hosts', function () {
-      expect(controller.concatZkNames(['host1', 'host2'], '2181')).to.equal('host1:2181,host2:2181');
-    });
-  });
-
-  describe('#getZkServerHosts()', function () {
-
-    beforeEach(function () {
-      controller.set('content', {});
-    });
-
-    afterEach(function () {
-      App.HostComponent.find.restore();
-    });
-
-    it('No ZooKeeper hosts, fromDeleteHost = false', function () {
-      sinon.stub(App.HostComponent, 'find', function () {
-        return []
-      });
-      controller.set('fromDeleteHost', false);
-      expect(controller.getZkServerHosts()).to.be.empty;
-    });
-
-    it('No ZooKeeper hosts, fromDeleteHost = true', function () {
-      sinon.stub(App.HostComponent, 'find', function () {
-        return []
-      });
-      controller.set('fromDeleteHost', true);
-      expect(controller.getZkServerHosts()).to.be.empty;
-      expect(controller.get('fromDeleteHost')).to.be.false;
-    });
-
-    it('One ZooKeeper host, fromDeleteHost = false', function () {
-      controller.set('fromDeleteHost', false);
-      sinon.stub(App.HostComponent, 'find', function () {
-        return [
-          {
-            id: 'ZOOKEEPER_SERVER_host1',
-            componentName: 'ZOOKEEPER_SERVER',
-            hostName: 'host1'
+      },
+      {
+        appGetterStubs: {
+          currentStackVersionNumber: '2.2'
+        },
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "hive-site": {
+            "hive.zookeeper.quorum": "host3:2181"
           }
-        ]
-      });
-      expect(controller.getZkServerHosts()).to.eql(['host1']);
-    });
-
-    it('One ZooKeeper host match current host name, fromDeleteHost = true', function () {
-      sinon.stub(App.HostComponent, 'find', function () {
-        return [
-          {
-            id: 'ZOOKEEPER_SERVER_host1',
-            componentName: 'ZOOKEEPER_SERVER',
-            hostName: 'host1'
+        },
+        m: 'hive.zookeeper.quorum property, current stack version is 2.2 property should be updated.',
+        e: {
+          configs: {
+            "hive-site": {
+              "hive.zookeeper.quorum": "host1:2181,host2:2181"
+            }
           }
-        ]
-      });
-      controller.set('fromDeleteHost', true);
-      controller.set('content.hostName', 'host1');
-      expect(controller.getZkServerHosts()).to.be.empty;
-      expect(controller.get('fromDeleteHost')).to.be.false;
-    });
-
-    it('One ZooKeeper host does not match current host name, fromDeleteHost = true', function () {
-      sinon.stub(App.HostComponent, 'find', function () {
-        return [
-          {
-            id: 'ZOOKEEPER_SERVER_host1',
-            componentName: 'ZOOKEEPER_SERVER',
-            hostName: 'host1'
+        }
+      },
+      {
+        appGetterStubs: {
+          currentStackVersionNumber: '2.1'
+        },
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "hive-site": {
+            "hive.zookeeper.quorum": "host3:2181"
           }
-        ]
+        },
+        m: 'hive.zookeeper.quorum property, current stack version is 2.1 property should be untouched.',
+        e: {
+          configs: {
+            "hive-site": {
+              "hive.zookeeper.quorum": "host3:2181"
+            }
+          }
+        }
+      },
+      {
+        appGetterStubs: {
+          currentStackVersionNumber: '2.1'
+        },
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "yarn-site": {
+            "hadoop.registry.zk.quorum": "host3:2181"
+          }
+        },
+        m: 'hadoop.registry.zk.quorum property, current stack version is 2.1 property should be untouched.',
+        e: {
+          configs: {
+            "yarn-site": {
+              "hadoop.registry.zk.quorum": "host3:2181"
+            }
+          }
+        }
+      },
+      {
+        appGetterStubs: {
+          currentStackVersionNumber: '2.2'
+        },
+        hostComponentModel: makeHostComponentModel('ZOOKEEPER_SERVER', ['host1', 'host2']),
+        configs: {
+          "yarn-site": {
+            "hadoop.registry.zk.quorum": "host3:2181"
+          }
+        },
+        m: 'hadoop.registry.zk.quorum property, current stack version is 2.2 property should be changed.',
+        e: {
+          configs: {
+            "yarn-site": {
+              "hadoop.registry.zk.quorum": "host1:2181,host2:2181"
+            }
+          }
+        }
+      }
+    ];
+
+    tests.forEach(function(test) {
+      it(test.m, function() {
+        if (test.appGetterStubs) {
+          Em.keys(test.appGetterStubs).forEach(function(key) {
+            sinon.stub(App, 'get').withArgs(key).returns(test.appGetterStubs[key]);
+          });
+        }
+        if (test.ctrlStubs) {
+          var stub = sinon.stub(controller, 'get');
+          Em.keys(test.ctrlStubs).forEach(function(key) {
+            stub.withArgs(key).returns(test.ctrlStubs[key]);
+          });
+        }
+        sinon.stub(App.HostComponent, 'find').returns(test.hostComponentModel);
+        controller.updateZkConfigs(test.configs);
+        expect(test.configs).to.be.eql(test.e.configs);
+        if (test.ctrlStubs) controller.get.restore();
+        if (test.appGetterStubs) App.get.restore();
+        App.HostComponent.find.restore();
       });
-      controller.set('fromDeleteHost', true);
-      controller.set('content.hostName', 'host2');
-      expect(controller.getZkServerHosts()[0]).to.equal("host1");
-      expect(controller.get('fromDeleteHost')).to.be.false;
     });
   });
 
@@ -3090,7 +3161,11 @@ describe('App.MainHostDetailsController', function () {
         Em.keys(item.input).forEach(function (key) {
           controller.set(key, item.input[key]);
         });
-        expect(controller.getHiveHosts().toArray()).to.eql(item.hiveHosts);
+        var hostsMap = controller.getHiveHosts().toArray();
+        var expectedHosts = hostsMap.filter(function(hostInfo) {
+          return ['WEBHCAT_SERVER', 'HIVE_METASTORE'].contains(hostInfo.component) && hostInfo.isInstalled === true;
+        }).mapProperty('hostName').uniq();
+        expect(expectedHosts).to.include.same.members(item.hiveHosts);
         expect(controller.get('hiveMetastoreHost')).to.be.empty;
         expect(controller.get('webhcatServerHost')).to.be.empty;
         expect(controller.get('fromDeleteHost')).to.be.false;
@@ -3412,4 +3487,297 @@ describe('App.MainHostDetailsController', function () {
     });
   });
 
+  describe('#onLoadHiveConfigs', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'saveConfigsBatch', Em.K);
+    });
+
+    afterEach(function() {
+      controller.saveConfigsBatch.restore();
+    });
+
+    var makeHostComponentModel = function(componentName, hostNames) {
+      if (Em.isArray(componentName)) {
+        return componentName.map(function(componentName, index) {
+          return makeHostComponentModel(componentName, hostNames[index]);
+        }).reduce(function(p,c) { return p.concat(c); }, []);
+      }
+      return hostNames.map(function(hostName) {
+        return {
+          componentName: componentName,
+          hostName: hostName
+        };
+      });
+    };
+
+    var makeFileNameProps = function(fileName, configs) {
+      var ret = {
+        type: fileName,
+        properties: {}
+      };
+      var propRet = {};
+      configs.forEach(function(property) {
+        propRet[property[0]] = property[1];
+      });
+      ret.properties = propRet;
+      return ret;
+    };
+
+    var makeEmptyPropAttrs = function() {
+      var fileNames = Array.prototype.slice.call(arguments);
+      var ret = {};
+      fileNames.forEach(function(fileName) {
+        ret[fileName] = {};
+      });
+      return ret;
+    };
+
+    var inlineComponentHostInfo = function(hostComponentModel) {
+      return hostComponentModel.mapProperty('componentName').uniq()
+        .map(function(componentName) {
+          return componentName + ":" + hostComponentModel.filterProperty('componentName', componentName).mapProperty('hostName').join();
+        }).join(',');
+    };
+
+    var tests = [
+      {
+        hostComponentModel: makeHostComponentModel(['HIVE_SERVER', 'HIVE_METASTORE'], [['host1', 'host2'], ['host1']]),
+        configs: {
+          items: [
+            makeFileNameProps('hive-site', [
+              ['hive.metastore.uris', 'thrift://host1:9090']
+            ]),
+            makeFileNameProps('hive-env', [
+              ['hive_user', 'hive_user_val'],
+              ['webhcat_user', 'webhcat_user_val']
+            ]),
+            makeFileNameProps('webhcat-site', [
+              ['templeton.hive.properties', 'hive.metastore.local=false,hive.metastore.uris=thrift://host1:9083,hive.metastore.sasl.enabled=false']
+            ]),
+            makeFileNameProps('core-site', [
+              ['hadoop.proxyuser.hive_user_val.hosts', 'host1'],
+              ['hadoop.proxyuser.webhcat_user_val.hosts', 'host1']
+            ])
+          ]
+        },
+        m: 'Components: {0}, appropriate configs should be changed, thrift port 9090, Controller stubs: {1}',
+        e: {
+          configs: [
+            {
+              "properties": {
+                "hive-site": makeFileNameProps('hive-site', [
+                  ['hive.metastore.uris', 'thrift://host1:9090']
+                ]).properties,
+                "webhcat-site": makeFileNameProps('webhcat-site', [
+                  ['templeton.hive.properties', 'hive.metastore.local=false,hive.metastore.uris=thrift://host1:9090,hive.metastore.sasl.enabled=false']
+                ]).properties,
+                "hive-env": makeFileNameProps('hive-env', [
+                  ['hive_user', 'hive_user_val'],
+                  ['webhcat_user', 'webhcat_user_val']
+                ]).properties
+              },
+              "properties_attributes": makeEmptyPropAttrs("hive-site", "webhcat-site", "hive-env")
+            },
+            {
+              "properties": {
+                "core-site": makeFileNameProps('core-site', [
+                  ['hadoop.proxyuser.hive_user_val.hosts', 'host1,host2'],
+                  ['hadoop.proxyuser.webhcat_user_val.hosts', 'host1,host2']
+                ]).properties
+              },
+              "properties_attributes": makeEmptyPropAttrs("core-site")
+            },
+          ]
+        }
+      },
+      {
+        hostComponentModel: makeHostComponentModel(['HIVE_SERVER', 'HIVE_METASTORE', 'WEBHCAT_SERVER'], [['host1', 'host2'], ['host1'], ['host2']]),
+        ctrlStubs: {
+          webhcatServerHost: 'host3'
+        },
+        configs: {
+          items: [
+            makeFileNameProps('hive-site', [
+              ['hive.metastore.uris', 'thrift://host1']
+            ]),
+            makeFileNameProps('hive-env', [
+              ['hive_user', 'hive_user_val'],
+              ['webhcat_user', 'webhcat_user_val']
+            ]),
+            makeFileNameProps('webhcat-site', [
+              ['templeton.hive.properties', 'hive.metastore.local=false,hive.metastore.uris=thrift://host1:9083,hive.metastore.sasl.enabled=false']
+            ]),
+            makeFileNameProps('core-site', [
+              ['hadoop.proxyuser.hive_user_val.hosts', 'host1'],
+              ['hadoop.proxyuser.webhcat_user_val.hosts', 'host1']
+            ])
+          ]
+        },
+        m: 'Components: {0}, appropriate configs should be changed, thrift port should be default 9083, Controller Stubs: {1}',
+        e: {
+          configs: [
+            {
+              "properties": {
+                "hive-site": makeFileNameProps('hive-site', [
+                  ['hive.metastore.uris', 'thrift://host1:9083,thrift://host2:9083,thrift://host3:9083']
+                ]).properties,
+                "webhcat-site": makeFileNameProps('webhcat-site', [
+                  ['templeton.hive.properties', 'hive.metastore.local=false,hive.metastore.uris=thrift://host1:9083\\,thrift://host2:9083\\,thrift://host3:9083,hive.metastore.sasl.enabled=false']
+                ]).properties,
+                "hive-env": makeFileNameProps('hive-env', [
+                  ['hive_user', 'hive_user_val'],
+                  ['webhcat_user', 'webhcat_user_val']
+                ]).properties
+              },
+              "properties_attributes": makeEmptyPropAttrs("hive-site", "webhcat-site", "hive-env")
+            },
+            {
+              "properties": {
+                "core-site": makeFileNameProps('core-site', [
+                  ['hadoop.proxyuser.hive_user_val.hosts', 'host1,host2,host3'],
+                  ['hadoop.proxyuser.webhcat_user_val.hosts', 'host1,host2,host3']
+                ]).properties
+              },
+              "properties_attributes": makeEmptyPropAttrs("core-site")
+            },
+          ]
+        }
+      },
+      {
+        hostComponentModel: makeHostComponentModel(['HIVE_SERVER', 'HIVE_METASTORE', 'WEBHCAT_SERVER'], [['host1'], ['host1'], ['host1']]),
+        ctrlStubs: {
+          webhcatServerHost: 'host3',
+          hiveMetastoreHost: 'host2'
+        },
+        configs: {
+          items: [
+            makeFileNameProps('hive-site', [
+              ['hive.metastore.uris', 'thrift://host1:1111']
+            ]),
+            makeFileNameProps('hive-env', [
+              ['hive_user', 'hive_user_val'],
+              ['webhcat_user', 'webhcat_user_val']
+            ]),
+            makeFileNameProps('webhcat-site', [
+              ['templeton.hive.properties', 'hive.metastore.local=false,hive.metastore.uris=thrift://host1:9083,hive.metastore.sasl.enabled=false']
+            ]),
+            makeFileNameProps('core-site', [
+              ['hadoop.proxyuser.hive_user_val.hosts', 'host1'],
+              ['hadoop.proxyuser.webhcat_user_val.hosts', 'host1']
+            ])
+          ]
+        },
+        m: 'Components: {0}, appropriate configs should be changed, thrift port should be 1111, Controller Stubs: {1}',
+        e: {
+          configs: [
+            {
+              "properties": {
+                "hive-site": makeFileNameProps('hive-site', [
+                  ['hive.metastore.uris', 'thrift://host1:1111,thrift://host2:1111,thrift://host3:1111']
+                ]).properties,
+                "webhcat-site": makeFileNameProps('webhcat-site', [
+                  ['templeton.hive.properties', 'hive.metastore.local=false,hive.metastore.uris=thrift://host1:1111\\,thrift://host2:1111\\,thrift://host3:1111,hive.metastore.sasl.enabled=false']
+                ]).properties,
+                "hive-env": makeFileNameProps('hive-env', [
+                  ['hive_user', 'hive_user_val'],
+                  ['webhcat_user', 'webhcat_user_val']
+                ]).properties
+              },
+              "properties_attributes": makeEmptyPropAttrs("hive-site", "webhcat-site", "hive-env")
+            },
+            {
+              "properties": {
+                "core-site": makeFileNameProps('core-site', [
+                  ['hadoop.proxyuser.hive_user_val.hosts', 'host1,host2,host3'],
+                  ['hadoop.proxyuser.webhcat_user_val.hosts', 'host1,host2,host3']
+                ]).properties
+              },
+              "properties_attributes": makeEmptyPropAttrs("core-site")
+            },
+          ]
+        }
+      },
+      {
+        hostComponentModel: makeHostComponentModel(['HIVE_SERVER', 'HIVE_METASTORE', 'WEBHCAT_SERVER'], [['host1', 'host2'], ['host1','host2'], ['host1', 'host3']]),
+        ctrlStubs: {
+          fromDeleteHost: true,
+          'content.hostName': 'host2',
+          webhcatServerHost: '',
+          hiveMetastoreHost: ''
+        },
+        configs: {
+          items: [
+            makeFileNameProps('hive-site', [
+              ['hive.metastore.uris', 'thrift://host1:1111']
+            ]),
+            makeFileNameProps('hive-env', [
+              ['hive_user', 'hive_user_val'],
+              ['webhcat_user', 'webhcat_user_val']
+            ]),
+            makeFileNameProps('webhcat-site', [
+              ['templeton.hive.properties', 'hive.metastore.local=false,hive.metastore.uris=thrift://host1:9083,hive.metastore.sasl.enabled=false']
+            ]),
+            makeFileNameProps('core-site', [
+              ['hadoop.proxyuser.hive_user_val.hosts', 'host1'],
+              ['hadoop.proxyuser.webhcat_user_val.hosts', 'host1']
+            ])
+          ]
+        },
+        m: 'Components: {0}, appropriate configs should be changed, thrift port should be default 9083, Controller Stubs: {1}',
+        e: {
+          configs: [
+            {
+              "properties": {
+                "hive-site": makeFileNameProps('hive-site', [
+                  ['hive.metastore.uris', 'thrift://host1:1111,thrift://host3:1111']
+                ]).properties,
+                "webhcat-site": makeFileNameProps('webhcat-site', [
+                  ['templeton.hive.properties', 'hive.metastore.local=false,hive.metastore.uris=thrift://host1:1111\\,thrift://host3:1111,hive.metastore.sasl.enabled=false']
+                ]).properties,
+                "hive-env": makeFileNameProps('hive-env', [
+                  ['hive_user', 'hive_user_val'],
+                  ['webhcat_user', 'webhcat_user_val']
+                ]).properties
+              },
+              "properties_attributes": makeEmptyPropAttrs("hive-site", "webhcat-site", "hive-env")
+            },
+            {
+              "properties": {
+                "core-site": makeFileNameProps('core-site', [
+                  ['hadoop.proxyuser.hive_user_val.hosts', 'host1,host3'],
+                  ['hadoop.proxyuser.webhcat_user_val.hosts', 'host1,host3']
+                ]).properties
+              },
+              "properties_attributes": makeEmptyPropAttrs("core-site")
+            },
+          ]
+        }
+      }
+    ];
+
+    tests.forEach(function(test) {
+      it(test.m.format(inlineComponentHostInfo(test.hostComponentModel), test.ctrlStubs ? JSON.stringify(test.ctrlStubs) : 'None'), function() {
+        if (test.appGetterStubs) {
+          Em.keys(test.appGetterStubs).forEach(function(key) {
+            sinon.stub(App, 'get').withArgs(key).returns(test.appGetterStubs[key]);
+          });
+        }
+        if (test.ctrlStubs) {
+          var stub = sinon.stub(controller, 'get');
+          Em.keys(test.ctrlStubs).forEach(function(key) {
+            stub.withArgs(key).returns(test.ctrlStubs[key]);
+          });
+        }
+        sinon.stub(App.HostComponent, 'find').returns(test.hostComponentModel);
+        controller.onLoadHiveConfigs(test.configs);
+        var configs = controller.saveConfigsBatch.args[0];
+        var properties = configs[0];
+        expect(properties).to.be.eql(test.e.configs);
+        if (test.ctrlStubs) controller.get.restore();
+        if (test.appGetterStubs) App.get.restore();
+        App.HostComponent.find.restore();
+      });
+    });
+  });
 });


Mime
View raw message