/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var App = require('app');
var validator = require('utils/validator');
var componentHelper = require('utils/component');
var batchUtils = require('utils/batch_scheduled_requests');
App.MainHostController = Em.ArrayController.extend({
name: 'mainHostController',
dataSource: App.Host.find(),
clearFilters: null,
filteredCount: 0,
/**
* flag responsible for updating status counters of hosts
*/
isCountersUpdating: false,
hostsCountMap: {},
/**
* Components which will be shown in component filter
* @returns {Array}
*/
componentsForFilter: function () {
var installedComponents = App.StackServiceComponent.find().toArray();
installedComponents.setEach('checkedForHostFilter', false);
return installedComponents;
}.property('App.router.clusterController.isLoaded'),
/**
* Master components
* @returns {Array}
*/
masterComponents: function () {
return this.get('componentsForFilter').filterProperty('isMaster', true);
}.property('componentsForFilter'),
/**
* Slave components
* @returns {Array}
*/
slaveComponents: function () {
return this.get('componentsForFilter').filterProperty('isSlave', true);
}.property('componentsForFilter'),
/**
* Client components
* @returns {Array}
*/
clientComponents: function () {
return this.get('componentsForFilter').filterProperty('isClient', true);
}.property('componentsForFilter'),
content: function () {
return this.get('dataSource').filterProperty('isRequested');
}.property('dataSource.@each.isRequested'),
/**
* filterProperties support follow types of filter:
* MATCH - match of RegExp
* EQUAL - equality "="
* MULTIPLE - multiple values to compare
* CUSTOM - substitute values with keys "{#}" in alias
*/
filterProperties: [
{
key: 'publicHostName',
alias: 'Hosts/host_name',
type: 'MATCH'
},
{
key: 'ip',
alias: 'Hosts/ip',
type: 'MATCH'
},
{
key: 'cpu',
alias: 'Hosts/cpu_count',
type: 'EQUAL'
},
{
key: 'memoryFormatted',
alias: 'Hosts/total_mem',
type: 'EQUAL'
},
{
key: 'loadAvg',
alias: 'metrics/load/load_one',
type: 'EQUAL'
},
{
key: 'hostComponents',
alias: 'host_components/HostRoles/component_name',
type: 'MULTIPLE'
},
{
key: 'healthClass',
alias: 'Hosts/host_status',
type: 'EQUAL'
},
{
key: 'criticalAlertsCount',
alias: 'alerts/summary/CRITICAL{0}|alerts/summary/WARNING{1}',
type: 'CUSTOM'
},
{
key: 'componentsWithStaleConfigsCount',
alias: 'host_components/HostRoles/stale_configs',
type: 'EQUAL'
},
{
key: 'componentsInPassiveStateCount',
alias: 'host_components/HostRoles/maintenance_state',
type: 'EQUAL'
},
{
key: 'selected',
alias: 'Hosts/host_name',
type: 'MULTIPLE'
}
],
viewProperties: [
Em.Object.create({
key: 'displayLength',
getValue: function (controller) {
var name = controller.get('name');
var dbValue = App.db.getDisplayLength(name);
if (Em.isNone(this.get('viewValue'))) {
this.set('viewValue', dbValue || '25'); //25 is default displayLength value for hosts page
}
return this.get('viewValue');
},
viewValue: null,
alias: 'page_size'
}),
Em.Object.create({
key: 'startIndex',
getValue: function (controller) {
var name = controller.get('name');
var startIndex = App.db.getStartIndex(name);
var value = this.get('viewValue');
if (Em.isNone(value)) {
if (Em.isNone(startIndex)) {
value = 0;
} else {
value = startIndex;
}
}
return (value > 0) ? value - 1 : value;
},
viewValue: null,
alias: 'from'
})
],
sortProps: [
{
key: 'publicHostName',
alias: 'Hosts/host_name'
},
{
key: 'ip',
alias: 'Hosts/ip'
},
{
key: 'cpu',
alias: 'Hosts/cpu_count'
},
{
key: 'memoryFormatted',
alias: 'Hosts/total_mem'
},
{
key: 'diskUsage',
//TODO disk_usage is relative property and need support from API, metrics/disk/disk_free used temporarily
alias: 'metrics/disk/disk_free'
},
{
key: 'loadAvg',
alias: 'metrics/load/load_one'
}
],
/**
* Validate and convert input string to valid url parameter.
* Detect if user have passed string as regular expression or extend
* string to regexp.
*
* @param {String} value
* @return {String}
**/
getRegExp: function (value) {
value = validator.isValidMatchesRegexp(value) ? value.replace(/(\.+\*?|(\.\*)+)$/, '') + '.*' : '^$';
value = /^\.\*/.test(value) || value == '^$' ? value : '.*' + value;
return value;
},
/**
* get query parameters computed from filter properties, sort properties and custom properties of view
* @return {Array}
*/
getQueryParameters: function () {
var queryParams = [];
var savedFilterConditions = App.db.getFilterConditions(this.get('name')) || [];
var savedSortConditions = App.db.getSortingStatuses(this.get('name')) || [];
var colPropAssoc = this.get('colPropAssoc');
var filterProperties = this.get('filterProperties');
var sortProperties = this.get('sortProps');
this.get('viewProperties').forEach(function (property) {
queryParams.push({
key: property.get('alias'),
value: property.getValue(this),
type: 'EQUAL'
})
}, this);
savedFilterConditions.forEach(function (filter) {
var property = filterProperties.findProperty('key', colPropAssoc[filter.iColumn]);
if (property && filter.value.length > 0 && !filter.skipFilter) {
var result = {
key: property.alias,
value: filter.value,
type: property.type
};
if (filter.type === 'string' && sortProperties.someProperty('key', colPropAssoc[filter.iColumn])) {
result.value = this.getRegExp(filter.value);
}
if (filter.type === 'number' || filter.type === 'ambari-bandwidth') {
result.type = this.getComparisonType(filter.value);
result.value = this.getProperValue(filter.value);
}
if (filter.type === 'ambari-bandwidth') {
result.value = this.convertMemory(filter.value);
}
if (result.value) {
queryParams.push(result);
}
}
}, this);
savedSortConditions.forEach(function (sort) {
var property = sortProperties.findProperty('key', sort.name);
if (property && (sort.status === 'sorting_asc' || sort.status === 'sorting_desc')) {
queryParams.push({
key: property.alias,
value: sort.status.replace('sorting_', ''),
type: 'SORT'
});
}
});
return queryParams;
},
/**
* update status counters of hosts
*/
updateStatusCounters: function () {
var self = this;
if (this.get('isCountersUpdating')) {
App.ajax.send({
name: 'host.status.counters',
sender: this,
data: {},
success: 'updateStatusCountersSuccessCallback',
error: 'updateStatusCountersErrorCallback'
});
setTimeout(function () {
self.updateStatusCounters();
}, App.get('componentsUpdateInterval'));
}
},
/**
* success callback on updateStatusCounters()
* map counters' value to categories
* @param data
*/
updateStatusCountersSuccessCallback: function (data) {
var hostsCountMap = {
'HEALTHY': data.Clusters.health_report['Host/host_status/HEALTHY'],
'UNHEALTHY': data.Clusters.health_report['Host/host_status/UNHEALTHY'],
'ALERT': data.Clusters.health_report['Host/host_status/ALERT'],
'UNKNOWN': data.Clusters.health_report['Host/host_status/UNKNOWN'],
'health-status-WITH-ALERTS': (data.alerts) ? data.alerts.summary.CRITICAL + data.alerts.summary.WARNING : 0,
'health-status-RESTART': data.Clusters.health_report['Host/stale_config'],
'health-status-PASSIVE_STATE': data.Clusters.health_report['Host/maintenance_state'],
'TOTAL': data.Clusters.total_hosts
};
this.set('hostsCountMap', hostsCountMap);
},
/**
* success callback on updateStatusCounters()
*/
updateStatusCountersErrorCallback: function() {
console.warn('ERROR: updateStatusCounters failed')
},
/**
* Return value without predicate
* @param {String} value
* @return {String}
*/
getProperValue: function (value) {
return (value.charAt(0) === '>' || value.charAt(0) === '<' || value.charAt(0) === '=') ? value.substr(1, value.length) : value;
},
/**
* Return value converted to kilobytes
* @param {String} value
* @return {*}
*/
convertMemory: function (value) {
var scale = value.charAt(value.length - 1);
// first char may be predicate for comparison
value = this.getProperValue(value);
var parsedValue = parseFloat(value);
if (isNaN(parsedValue)) {
return value;
}
switch (scale) {
case 'g':
parsedValue *= 1048576;
break;
case 'm':
parsedValue *= 1024;
break;
case 'k':
break;
default:
//default value in GB
parsedValue *= 1048576;
}
return Math.round(parsedValue);
},
/**
* Return comparison type depending on populated predicate
* @param value
* @return {String}
*/
getComparisonType: function (value) {
var comparisonChar = value.charAt(0);
var result = 'EQUAL';
if (isNaN(comparisonChar)) {
switch (comparisonChar) {
case '>':
result = 'MORE';
break;
case '<':
result = 'LESS';
break;
}
}
return result;
},
/**
* Filter hosts by componentName of component
* @param {App.HostComponent} component
*/
filterByComponent: function (component) {
if (!component)
return;
var id = component.get('componentName');
var column = 6;
this.get('componentsForFilter').setEach('checkedForHostFilter', false);
var filterForComponent = {
iColumn: column,
value: [id],
type: 'multiple'
};
App.db.setFilterConditions(this.get('name'), [filterForComponent]);
},
/**
* On click callback for delete button
*/
deleteButtonPopup: function () {
var self = this;
App.showConfirmationPopup(function () {
self.removeHosts();
});
},
showAlertsPopup: function (event) {
var host = event.context;
App.router.get('mainAlertsController').loadAlerts(host.get('hostName'), "HOST");
App.ModalPopup.show({
header: this.t('services.alerts.headingOfList'),
bodyClass: Ember.View.extend({
templateName: require('templates/main/host/alerts_popup'),
controllerBinding: 'App.router.mainAlertsController',
alerts: function () {
return this.get('controller.alerts');
}.property('controller.alerts'),
closePopup: function () {
this.get('parentView').hide();
}
}),
primary: Em.I18n.t('common.close'),
secondary: null,
didInsertElement: function () {
this.$().find('.modal-footer').addClass('align-center');
this.$().children('.modal').css({'margin-top': '-350px'});
}
});
event.stopPropagation();
},
/**
* remove selected hosts
*/
removeHosts: function () {
var hosts = this.get('content');
var selectedHosts = hosts.filterProperty('isChecked', true);
selectedHosts.forEach(function (_hostInfo) {
console.log('Removing: ' + _hostInfo.hostName);
});
this.get('fullContent').removeObjects(selectedHosts);
},
/**
* remove hosts with id equal host_id
* @param {String} host_id
*/
checkRemoved: function (host_id) {
var hosts = this.get('content');
var selectedHosts = hosts.filterProperty('id', host_id);
this.get('fullContent').removeObjects(selectedHosts);
},
/**
* Bulk operation wrapper
* @param {Object} operationData - data about bulk operation (action, hosts or hostComponents etc)
* @param {Array} hosts - list of affected hosts
*/
bulkOperation: function (operationData, hosts) {
if (operationData.componentNameFormatted) {
if (operationData.action === 'RESTART') {
this.bulkOperationForHostComponentsRestart(operationData, hosts);
}
else {
if (operationData.action.indexOf('DECOMMISSION') != -1) {
this.bulkOperationForHostComponentsDecommission(operationData, hosts);
}
else {
this.bulkOperationForHostComponents(operationData, hosts);
}
}
}
else {
if (operationData.action === 'RESTART') {
this.bulkOperationForHostsRestart(operationData, hosts);
}
else {
if (operationData.action === 'PASSIVE_STATE') {
this.bulkOperationForHostsPassiveState(operationData, hosts);
}
else {
this.bulkOperationForHosts(operationData, hosts);
}
}
}
},
/**
* Bulk operation (start/stop all) for selected hosts
* @param {Object} operationData - data about bulk operation (action, hostComponents etc)
* @param {Array} hosts - list of affected hosts
*/
bulkOperationForHosts: function (operationData, hosts) {
var self = this;
batchUtils.getComponentsFromServer({
hosts: hosts.mapProperty('hostName'),
workStatus: operationData.actionToCheck,
passiveState: 'OFF',
displayParams: ['host_components/HostRoles/component_name']
}, function (data) {
self.bulkOperationForHostsCallback(operationData, data);
});
},
/**
* run Bulk operation (start/stop all) for selected hosts
* after host and components are loaded
* @param operationData
* @param data
*/
bulkOperationForHostsCallback: function (operationData, data) {
var query = [];
var hostNames = [];
var hostsMap = {};
data.items.forEach(function (host) {
host.host_components.forEach(function (hostComponent) {
if (!App.components.get('clients').contains((hostComponent.HostRoles.component_name))) {
if (hostsMap[host.Hosts.host_name]) {
hostsMap[host.Hosts.host_name].push(hostComponent.HostRoles.component_name);
} else {
hostsMap[host.Hosts.host_name] = [hostComponent.HostRoles.component_name];
}
}
});
});
for (var hostName in hostsMap) {
var subQuery = '(HostRoles/component_name.in(%@)&HostRoles/host_name=' + hostName + ')';
var components = hostsMap[hostName];
if (components.length) {
query.push(subQuery.fmt(components.join(',')));
}
hostNames.push(hostName);
}
hostNames = hostNames.join(",");
if (query.length) {
query = query.join('|');
App.ajax.send({
name: 'bulk_request.hosts.all_components',
sender: this,
data: {
query: query,
state: operationData.action,
requestInfo: operationData.message,
hostName: hostNames
},
success: 'bulkOperationForHostComponentsSuccessCallback'
});
}
else {
App.ModalPopup.show({
header: Em.I18n.t('rolling.nothingToDo.header'),
body: Em.I18n.t('rolling.nothingToDo.body').format(Em.I18n.t('hosts.host.maintainance.allComponents.context')),
secondary: false
});
}
},
/**
* Bulk restart for selected hosts
* @param {Object} operationData - data about bulk operation (action, hostComponents etc)
* @param {Ember.Enumerable} hosts - list of affected hosts
*/
bulkOperationForHostsRestart: function (operationData, hosts) {
batchUtils.getComponentsFromServer({
passiveState: 'OFF',
hosts: hosts.mapProperty('hostName'),
displayParams: ['host_components/HostRoles/component_name']
}, function (data) {
var hostComponents = [];
data.items.forEach(function (host) {
host.host_components.forEach(function (hostComponent) {
hostComponents.push(Em.Object.create({
componentName: hostComponent.HostRoles.component_name,
hostName: host.Hosts.host_name
}));
})
});
batchUtils.restartHostComponents(hostComponents, Em.I18n.t('rollingrestart.context.allOnSelectedHosts'), "HOST");
});
},
/**
* Bulk turn on/off passive state for selected hosts
* @param {Object} operationData - data about bulk operation (action, hostComponents etc)
* @param {Array} hosts - list of affected hosts
*/
bulkOperationForHostsPassiveState: function (operationData, hosts) {
var self = this;
batchUtils.getComponentsFromServer({
hosts: hosts.mapProperty('hostName'),
displayParams: ['Hosts/maintenance_state']
}, function (data) {
var hostNames = [];
data.items.forEach(function (host) {
if (host.Hosts.maintenance_state !== operationData.state) {
hostNames.push(host.Hosts.host_name);
}
});
if (hostNames.length) {
App.ajax.send({
name: 'bulk_request.hosts.passive_state',
sender: self,
data: {
hostNames: hostNames.join(','),
passive_state: operationData.state,
requestInfo: operationData.message
},
success: 'updateHostPassiveState'
});
} else {
App.ModalPopup.show({
header: Em.I18n.t('rolling.nothingToDo.header'),
body: Em.I18n.t('hosts.bulkOperation.passiveState.nothingToDo.body'),
secondary: false
});
}
});
},
updateHostPassiveState: function (data, opt, params) {
batchUtils.infoPassiveState(params.passive_state);
},
/**
* Bulk operation for selected hostComponents
* @param {Object} operationData - data about bulk operation (action, hostComponents etc)
* @param {Array} hosts - list of affected hosts
*/
bulkOperationForHostComponents: function (operationData, hosts) {
var self = this;
batchUtils.getComponentsFromServer({
components: [operationData.componentName],
hosts: hosts.mapProperty('hostName'),
passiveState: 'OFF'
}, function (data) {
if (data.items.length) {
var hostsWithComponentInProperState = data.items.mapProperty('Hosts.host_name');
App.ajax.send({
name: 'bulk_request.host_components',
sender: self,
data: {
hostNames: hostsWithComponentInProperState.join(','),
state: operationData.action,
requestInfo: operationData.message + ' ' + operationData.componentNameFormatted,
componentName: operationData.componentName
},
success: 'bulkOperationForHostComponentsSuccessCallback'
});
}
else {
App.ModalPopup.show({
header: Em.I18n.t('rolling.nothingToDo.header'),
body: Em.I18n.t('rolling.nothingToDo.body').format(operationData.componentNameFormatted),
secondary: false
});
}
});
},
/**
* Bulk decommission/recommission for selected hostComponents
* @param {Object} operationData
* @param {Array} hosts
*/
bulkOperationForHostComponentsDecommission: function (operationData, hosts) {
var self = this;
batchUtils.getComponentsFromServer({
components: [operationData.realComponentName],
hosts: hosts.mapProperty('hostName'),
passiveState: 'OFF',
displayParams: ['host_components/HostRoles/state']
}, function (data) {
self.bulkOperationForHostComponentsDecommissionCallBack(operationData, data)
});
},
/**
* run Bulk decommission/recommission for selected hostComponents
* after host and components are loaded
* @param operationData
* @param data
*/
bulkOperationForHostComponentsDecommissionCallBack: function (operationData, data) {
var service = App.Service.find(operationData.serviceName);
var components = [];
data.items.forEach(function (host) {
host.host_components.forEach(function (hostComponent) {
components.push(Em.Object.create({
componentName: hostComponent.HostRoles.component_name,
hostName: host.Hosts.host_name,
workStatus: hostComponent.HostRoles.state
}))
});
});
if (components.length) {
var hostsWithComponentInProperState = components.mapProperty('hostName');
var turn_off = operationData.action.indexOf('OFF') !== -1;
var svcName = operationData.serviceName;
var masterName = operationData.componentName;
var slaveName = operationData.realComponentName;
var hostNames = hostsWithComponentInProperState.join(',');
if (turn_off) {
// For recommession
if (svcName === "YARN" || svcName === "HBASE" || svcName === "HDFS") {
App.router.get('mainHostDetailsController').doRecommissionAndStart(hostNames, svcName, masterName, slaveName);
}
else if (svcName === "MAPREDUCE") {
App.router.get('mainHostDetailsController').doRecommissionAndRestart(hostNames, svcName, masterName, slaveName);
}
} else {
hostsWithComponentInProperState = components.filterProperty('workStatus', 'STARTED').mapProperty('hostName');
//For decommession
if (svcName == "HBASE") {
// HBASE service, decommission RegionServer in batch requests
App.router.get('mainHostDetailsController').doDecommissionRegionServer(hostNames, svcName, masterName, slaveName);
} else {
var parameters = {
"slave_type": slaveName
};
var contextString = turn_off ? 'hosts.host.' + slaveName.toLowerCase() + '.recommission' :
'hosts.host.' + slaveName.toLowerCase() + '.decommission';
if (turn_off) {
parameters['included_hosts'] = hostsWithComponentInProperState.join(',')
}
else {
parameters['excluded_hosts'] = hostsWithComponentInProperState.join(',');
}
App.ajax.send({
name: 'bulk_request.decommission',
sender: this,
data: {
context: Em.I18n.t(contextString),
serviceName: service.get('serviceName'),
componentName: operationData.componentName,
parameters: parameters
},
success: 'bulkOperationForHostComponentsSuccessCallback'
});
}
}
}
else {
App.ModalPopup.show({
header: Em.I18n.t('rolling.nothingToDo.header'),
body: Em.I18n.t('rolling.nothingToDo.body').format(operationData.componentNameFormatted),
secondary: false
});
}
},
/**
* Bulk restart for selected hostComponents
* @param {Object} operationData
* @param {Array} hosts
*/
bulkOperationForHostComponentsRestart: function (operationData, hosts) {
var service = App.Service.find(operationData.serviceName);
batchUtils.getComponentsFromServer({
components: [operationData.componentName],
hosts: hosts.mapProperty('hostName'),
passiveState: 'OFF',
displayParams: ['Hosts/maintenance_state', 'host_components/HostRoles/stale_configs', 'host_components/HostRoles/maintenance_state']
}, function (data) {
var wrappedHostComponents = [];
data.items.forEach(function (host) {
host.host_components.forEach(function (hostComponent) {
wrappedHostComponents.push(Em.Object.create({
componentName: hostComponent.HostRoles.component_name,
hostName: host.Hosts.host_name,
hostPassiveState: host.Hosts.maintenance_state,
staleConfigs: hostComponent.HostRoles.stale_configs,
passiveState: hostComponent.HostRoles.maintenance_state
}))
});
});
if (wrappedHostComponents.length) {
batchUtils.showRollingRestartPopup(wrappedHostComponents.objectAt(0).get('componentName'), service.get('displayName'), service.get('passiveState') === "ON", false, wrappedHostComponents);
} else {
App.ModalPopup.show({
header: Em.I18n.t('rolling.nothingToDo.header'),
body: Em.I18n.t('rolling.nothingToDo.body').format(operationData.componentNameFormatted),
secondary: false
});
}
});
},
updateHostComponentsPassiveState: function (data, opt, params) {
batchUtils.infoPassiveState(params.passive_state);
},
/**
* Show BO popup after bulk request
*/
bulkOperationForHostComponentsSuccessCallback: function () {
App.router.get('applicationController').dataLoading().done(function (initValue) {
if (initValue) {
App.router.get('backgroundOperationsController').showPopup();
}
});
},
/**
* associations between host property and column index
* @type {Array}
*/
colPropAssoc: function () {
var associations = [];
associations[0] = 'healthClass';
associations[1] = 'publicHostName';
associations[2] = 'ip';
associations[3] = 'cpu';
associations[4] = 'memoryFormatted';
associations[5] = 'loadAvg';
associations[6] = 'hostComponents';
associations[7] = 'criticalAlertsCount';
associations[8] = 'componentsWithStaleConfigsCount';
associations[9] = 'componentsInPassiveStateCount';
associations[10] = 'selected';
return associations;
}.property()
});