Return-Path: X-Original-To: apmail-ambari-commits-archive@www.apache.org Delivered-To: apmail-ambari-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 9CBD3184A8 for ; Wed, 23 Dec 2015 15:06:41 +0000 (UTC) Received: (qmail 36215 invoked by uid 500); 23 Dec 2015 15:06:41 -0000 Delivered-To: apmail-ambari-commits-archive@ambari.apache.org Received: (qmail 36083 invoked by uid 500); 23 Dec 2015 15:06:41 -0000 Mailing-List: contact commits-help@ambari.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: ambari-dev@ambari.apache.org Delivered-To: mailing list commits@ambari.apache.org Received: (qmail 35846 invoked by uid 99); 23 Dec 2015 15:06:41 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 23 Dec 2015 15:06:41 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id C64D7E0B20; Wed, 23 Dec 2015 15:06:40 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: ncole@apache.org To: commits@ambari.apache.org Date: Wed, 23 Dec 2015 15:06:47 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [08/51] [abbrv] ambari git commit: AMBARI-14416. Refactor Host Details controller 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 hosts_with_components-initializer + * Used for configs with value equal to the hosts list + * May set value as array (if asArray is true) or as comma-sepratated string (if asArray 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 initializer.asArray (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 host_with_component-initializer + * Used for configs with value equal to hostName that has component + * Value may be modified with if withModifier is true (it is by default) + * hostWithPort-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 getSimpleComponentConfig, but with possibility to modify replaceWith-value + * prefix is added before it + * suffix is added after it + * hostWithPrefix-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 host_with_port-initializer + * Used for configs with value equal to hostName where some component exists concatenated with port-value + * Port-value is calculated according to port and portFromDependencies values + * If portFromDependencies is true, port-value is used as key of the dependencies (where real port-value is) + * Otherwise - port-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 initializer.modifier) + * Port-value is calculated according to initializer.portKey or initializer.port 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 hosts_with_port-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 port and portFromDependencies values + * If portFromDependencies is true, port-value is used as key of the dependencies (where real port-value is) + * Otherwise - port-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 portFromDependencies is false this value is used as port for hosts + * if portFromDependencies is true `port` is used as key in the dependencies to get real port-value + * @param {boolean} portFromDependencies=false true - use port as key for dependencies to get real port-value, + * false - use port 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 initializer.modifier) + * Delimiter between hostNames also may be customized in the initializer.modifier + * Port-value is calculated according to initializer.portKey or initializer.port 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 dependencies accorfing to initializer.portKey or initializer.port 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 initializer.components 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 initializer.components 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 _defaultWinReplace, 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 App.Host-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: + *
    + *
  • Should has available space
  • + *
  • Should not be home-dir
  • + *
  • Should not be docker-dir
  • + *
  • Should not be boot-dir
  • + *
  • Should not be dev-dir
  • + *
+ * + * @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 single_mountpoint-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 multiple_mountpoints-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 rm_hosts_with_port 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(); + }); + }); + }); });