blob: a7c9db5b1c7ee4300331710ffc27e59143338ee3 [file] [log] [blame]
/**
* 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.
*/
/**
* @typedef {object} BlueprintObject
* @property {BlueprintMappings} blueprint
* @property {BlueprintClusterBindings} blueprint_cluster_bindings
*/
/**
* @typedef {object} BlueprintMappings
* @property {BlueprintMappingsHostGroup[]} host_groups
*/
/**
* @typedef {object[]} BlueprintMappingsHostGroup
* @property {BlueprintHostGroupComponent[]} components
* @property {string} name host group name
*/
/**
* @typedef {object} BlueprintHostGroupComponent
* @property {string} name component name
*/
/**
* @typedef {object} BlueprintClusterBindings
* @property {BlueprintClusterBindingsHostGroup[]} host_groups
*/
/**
* @typedef {object} BlueprintClusterBindingsHostGroup
* @property {BlueprintClusterBindingsHostGroupHosts[]} hosts
* @property {string} name host group name
*/
/**
* @typedef {object} BlueprintClusterBindingsHostGroupHosts
* @property {string} fqdn host fqdn
*/
module.exports = {
/**
* @param {BlueprintObject} masterBlueprint
* @param {BlueprintObject} slaveBlueprint
* @return {BlueprintObject}
*/
mergeBlueprints: function(masterBlueprint, slaveBlueprint) {
console.time('mergeBlueprints');
var self = this;
// Check edge cases
if (!slaveBlueprint && !masterBlueprint) {
throw 'slaveBlueprint or masterBlueprint should not be empty';
}
else
if (slaveBlueprint && !masterBlueprint) {
return slaveBlueprint;
}
else
if (!slaveBlueprint && masterBlueprint) {
return masterBlueprint;
}
// Work with main case (both blueprint are presented)
var matches = self.matchGroups(masterBlueprint, slaveBlueprint);
var res = {
blueprint: { host_groups: [] },
blueprint_cluster_binding: { host_groups: [] }
};
var tmpObj = {hosts: [], components: []};
var masterBluePrintHostGroupsCluster = this.blueprintToObject(masterBlueprint, 'blueprint_cluster_binding.host_groups');
var slaveBluePrintHostGroupsCluster = this.blueprintToObject(slaveBlueprint, 'blueprint_cluster_binding.host_groups');
var masterBluePrintHostGroupsBlueprint = this.blueprintToObject(masterBlueprint, 'blueprint.host_groups');
var slaveBluePrintHostGroupsBlueprint = this.blueprintToObject(slaveBlueprint, 'blueprint.host_groups');
matches.forEach(function (match, i) {
var group_name = 'host-group-' + (i + 1);
var masterComponents = match.g1 ? Em.getWithDefault(masterBluePrintHostGroupsBlueprint, match.g1, tmpObj).components : [];
var slaveComponents = match.g2 ? Em.getWithDefault(slaveBluePrintHostGroupsBlueprint, match.g2, tmpObj).components : [];
res.blueprint.host_groups.push({
name: group_name,
components: masterComponents.concat(slaveComponents)
});
var hosts = match.g1 ? Em.getWithDefault(masterBluePrintHostGroupsCluster, match.g1, tmpObj).hosts :
Em.getWithDefault(slaveBluePrintHostGroupsCluster, match.g2, tmpObj).hosts;
res.blueprint_cluster_binding.host_groups.push({
name: group_name,
hosts: hosts
});
});
console.timeEnd('mergeBlueprints');
return res;
},
/**
* Convert <code>blueprint</code>-object to the array with keys equal to the host-groups names
* Used to improve performance when user try to search value in the blueprint using host-group name as search-field
* Example:
* Before:
* <pre>
* // blueprint
* {
* blueprint: {
* host_groups: [
* {
* components: [{}, {}, ...],
* name: 'n1'
* },
* {
* components: [{}, {}, ...],
* name: 'n2'
* }
* ]
* },
* blueprint_cluster_binding: {
* host_groups: [
* {
* hosts: [{}, {}, ...],
* name: 'n1'
* },
* {
* hosts: [{}, {}, ...],
* name: 'n2'
* }
* ]
* }
* }
* </pre>
* Return:
* <pre>
* // field = 'blueprint_cluster_binding.host_groups'
* {
* n1: {
* hosts: [{}, {}, ...],
* name: 'n1'
* },
* n2: {
* hosts: [{}, {}, ...],
* name: 'n2'
* }
* }
*
* // field = 'blueprint.host_groups'
* {
* n1: {
* components: [{}, {}, ...],
* name: 'n1'
* },
* n2: {
* components: [{}, {}, ...],
* name: 'n2'
* }
* }
* </pre>
* @param {object} blueprint
* @param {string} field
* @returns {object}
*/
blueprintToObject: function(blueprint, field) {
var valueToMap = Em.get(blueprint, field);
if (!Array.isArray(valueToMap)) {
return {};
}
return valueToMap.toMapByProperty('name');
},
matchGroups: function(masterBlueprint, slaveBlueprint) {
var self = this;
var res = [];
var groups1 = masterBlueprint.blueprint_cluster_binding.host_groups;
var groups2 = slaveBlueprint.blueprint_cluster_binding.host_groups;
var groups1_used = groups1.map(function() { return false; });
var groups2_used = groups2.map(function() { return false; });
self.matchGroupsWithLeft(groups1, groups2, groups1_used, groups2_used, res, false);
self.matchGroupsWithLeft(groups2, groups1, groups2_used, groups1_used, res, true);
return res;
},
matchGroupsWithLeft: function(groups1, groups2, groups1_used, groups2_used, res, inverse) {
var gs2 = this.groupsToObject(groups2);
for (var i = 0; i < groups1.length; i++) {
if (groups1_used[i]) {
continue;
}
var group1 = groups1[i];
groups1_used[i] = true;
var group2 = gs2[group1.hosts.mapProperty('fqdn').join(',')];
if (group2) {
groups2_used[group2.index] = true;
}
var item = {};
if (inverse) {
item.g2 = group1.name;
if (group2) {
item.g1 = group2.name;
}
}
else {
item.g1 = group1.name;
if (group2) {
item.g2 = group2.name;
}
}
res.push(item);
}
// remove unneeded property
groups2.forEach(function(group) {
delete group.index;
});
},
/**
* Convert array of objects to the one object to improve performance with searching objects in the provided array
* Example:
* Before:
* <pre>
* // groups
* [
* {
* hosts: [
* {fqdn: 'h1'}, {fqdn: 'h2'}
* ],
* name: 'n1'
* },
* {
* hosts: [
* {fqdn: 'h3'}, {fqdn: 'h4'}
* ]
* }
* ]
* </pre>
* Return:
* <pre>
* {
* 'h1,h2': {
* hosts: [
* {fqdn: 'h1'}, {fqdn: 'h2'}
* ],
* name: 'n1',
* index: 0
* },
* 'h3,h4': {
* hosts: [
* {fqdn: 'h3'}, {fqdn: 'h4'}
* ],
* name: 'n2',
* index: 1
* }
* }
* </pre>
* @param {{hosts: object[], name: string}[]} groups
* @returns {object}
*/
groupsToObject: function (groups) {
var ret = {};
groups.forEach(function (group, index) {
var key = group.hosts.mapProperty('fqdn').join(',');
ret[key] = group;
ret[key].index = index;
});
return ret;
},
/**
* Remove from blueprint all components expect given components
* @param {object} blueprint
* @param {string[]} components
*/
filterByComponents: function(blueprint, components) {
if (!blueprint) {
return null;
}
var res = JSON.parse(JSON.stringify(blueprint));
var emptyGroups = [];
for (var i = 0; i < res.blueprint.host_groups.length; i++) {
res.blueprint.host_groups[i].components = res.blueprint.host_groups[i].components.filter(function(c) {
return components.contains(c.name);
});
if (res.blueprint.host_groups[i].components.length == 0) {
emptyGroups.push(res.blueprint.host_groups[i].name);
}
}
res.blueprint.host_groups = res.blueprint.host_groups.filter(function(g) {
return !emptyGroups.contains(g.name);
});
res.blueprint_cluster_binding.host_groups = res.blueprint_cluster_binding.host_groups.filter(function(g) {
return !emptyGroups.contains(g.name);
});
return res;
},
addComponentsToBlueprint: function(blueprint, components) {
var res = JSON.parse(JSON.stringify(blueprint));
res.blueprint.host_groups.forEach(function(group) {
components.forEach(function(component) {
group.components.push({ name: component });
});
});
return res;
},
/**
* @method buildConfigsJSON - generates JSON according to blueprint format
* @param {Em.Array} stepConfigs - array of Ember Objects
* @returns {Object}
* Example:
* {
* "yarn-env": {
* "properties": {
* "content": "some value",
* "yarn_heapsize": "1024",
* "resourcemanager_heapsize": "1024",
* }
* },
* "yarn-log4j": {
* "properties": {
* "content": "some other value"
* }
* }
* }
*/
buildConfigsJSON: function (stepConfigs) {
var configurations = {};
stepConfigs.forEach(function (stepConfig) {
var configs = stepConfig.get('configs');
if (!configs) return false;
configs.forEach(function (config) {
var type = App.config.getConfigTagFromFileName(config.get('filename'));
if (!configurations[type]) {
configurations[type] = {properties: {}}
}
configurations[type]['properties'][config.get('name')] = config.get('value');
});
});
return configurations;
},
/**
* @method generateHostGroups
* @param {Array} hostNames - list of all hostNames
* @returns {{blueprint: {host_groups: Array}, blueprint_cluster_binding: {host_groups: Array}}}
*/
generateHostGroups: function(hostNames) {
var recommendations = {
blueprint: {
host_groups: []
},
blueprint_cluster_binding: {
host_groups: []
}
};
var hostsMap = this.getComponentForHosts();
for (var i = 0; i <= hostNames.length; i++) {
var host_group = {
name: "host-group-" + (i + 1),
components: []
};
var hcFiltered = hostsMap[hostNames[i]];
if (Em.isNone(hcFiltered)) continue;
for (var j = 0; j < hcFiltered.length; j++) {
host_group.components.push({
name: hcFiltered[j]
});
}
recommendations.blueprint.host_groups.push(host_group);
recommendations.blueprint_cluster_binding.host_groups.push({
name: "host-group-" + (i + 1),
hosts: [{
fqdn: hostNames[i]
}]
});
}
return recommendations;
},
/**
* Small helper method to update hostMap
* it perform update of object only
* if unique component per host is added
*
* @param {Object} hostMapObject
* @param {string[]} hostNames
* @param {string} componentName
* @returns {Object}
* @private
*/
_generateHostMap: function(hostMapObject, hostNames, componentName) {
Em.assert('hostMapObject, hostNames, componentName should be defined', !!hostMapObject && !!hostNames && !!componentName);
if (!hostNames.length) return hostMapObject;
hostNames.forEach(function(hostName) {
if (!hostMapObject[hostName])
hostMapObject[hostName] = [];
if (!hostMapObject[hostName].contains(componentName))
hostMapObject[hostName].push(componentName);
});
return hostMapObject;
},
/**
* Clean up host groups from components that should be removed
*
* @param hostGroups
* @param serviceNames
*/
removeDeletedComponents: function(hostGroups, serviceNames) {
var components = [];
App.StackService.find().forEach(function(s) {
if (serviceNames.contains(s.get('serviceName'))) {
components = components.concat(s.get('serviceComponents').mapProperty('componentName'));
}
});
hostGroups.blueprint.host_groups.forEach(function(hg) {
hg.components = hg.components.filter(function(c) {
return !components.contains(c.name);
})
});
return hostGroups;
},
/**
* collect all component names that are present on hosts
* @returns {object}
*/
getComponentForHosts: function() {
var hostsMap = {};
var componentModels = [App.ClientComponent, App.SlaveComponent, App.MasterComponent];
componentModels.forEach(function(_model){
_model.find().forEach(function(c) {
hostsMap = this._generateHostMap(hostsMap, c.get('hostNames'), c.get('componentName'));
}, this);
}, this);
this.changeHostsMapForConfigActions(hostsMap);
return hostsMap;
},
/**
* Adds or removes the component name entry as saved in App.componentToBeAdded and App.componentToBeDeleted from the 'hostsMap'
* @param hostsMap {object}
* @private
* @method {changeHostsMapForConfigActions}
*/
changeHostsMapForConfigActions: function(hostsMap) {
var componentToBeAdded = App.get('componentToBeAdded');
var componentToBeDeleted = App.get('componentToBeDeleted');
if (!App.isEmptyObject(componentToBeAdded)) {
hostsMap = this._generateHostMap(hostsMap, componentToBeAdded.get('hostNames'), componentToBeAdded.get('componentName'));
} else if (!App.isEmptyObject(componentToBeDeleted) && hostsMap[componentToBeDeleted.hostName]) {
var index = hostsMap[componentToBeDeleted.hostName].indexOf(componentToBeDeleted.componentName);
if (index > -1) {
hostsMap[componentToBeDeleted.hostName].splice(index, 1);
}
}
},
/**
* Returns host-group name by fqdn.
* Returns <code>null</code> when not found.
*
* @param {object} blueprint
* @param {string} fqdn
* @returns {string|null}
*/
getHostGroupByFqdn: function(blueprint, fqdn) {
return Em.getWithDefault(blueprint || {}, 'blueprint_cluster_binding.host_groups', [])
.filter(function(i) {
return Em.getWithDefault(i, 'hosts', []).mapProperty('fqdn').contains(fqdn);
})
.mapProperty('name')[0] || null;
},
/**
* Add component to specified host group.
*
* @param {object} blueprint
* @param {string} componentName
* @param {string} hostGroupName
* @return {object}
*/
addComponentToHostGroup: function(blueprint, componentName, hostGroupName) {
var hostGroup = blueprint.blueprint.host_groups.findProperty('name', hostGroupName);
if (hostGroup) {
if (!hostGroup.hasOwnProperty('components')) {
hostGroup.components = [];
}
if (!hostGroup.components.someProperty('name', componentName)) {
hostGroup.components.pushObject({name: componentName});
}
}
return blueprint;
}
};