mirror of https://github.com/rancher/ui.git
461 lines
18 KiB
JavaScript
461 lines
18 KiB
JavaScript
import { inject as service } from '@ember/service';
|
|
import { on } from '@ember/object/evented';
|
|
import { later } from '@ember/runloop';
|
|
import Component from '@ember/component';
|
|
import {
|
|
set, get, computed, observer, setProperties
|
|
} from '@ember/object';
|
|
import { alias } from '@ember/object/computed';
|
|
import { convertToMillis } from 'shared/utils/util';
|
|
import { parseSi } from 'shared/utils/parse-unit';
|
|
import ReservationCheck from 'shared/mixins/reservation-check';
|
|
import CatalogUpgrade from 'shared/mixins/catalog-upgrade';
|
|
import layout from './template';
|
|
|
|
const EXPOSED_OPTIONS = ['exporter-node.enabled', 'exporter-node.ports.metrics.port',
|
|
'exporter-kubelets.https', 'prometheus.retention', 'grafana.persistence.enabled',
|
|
'prometheus.persistence.enabled', 'prometheus.persistence.storageClass',
|
|
'grafana.persistence.storageClass', 'grafana.persistence.size',
|
|
'prometheus.persistence.size', 'prometheus.resources.core.requests.cpu',
|
|
'exporter-node.resources.limits.cpu', 'exporter-node.resources.limits.memory',
|
|
'prometheus.resources.core.limits.cpu', 'prometheus.resources.core.requests.memory',
|
|
'prometheus.resources.core.limits.memory', 'operator.resources.limits.memory'];
|
|
|
|
const NODE_EXPORTER_CPU = 100;
|
|
const NODE_EXPORTER_MEMORY = 30;
|
|
const CLUSTER_CPU = 900;
|
|
const CLUSTER_MEMORY = 970;
|
|
const MONITORING_TEMPLATE = 'system-library-rancher-monitoring';
|
|
|
|
const CLUSTER_HIDDEN_KEYS = { 'operator-init.enabled': 'true', }
|
|
|
|
const PROMETHEUS_TOLERATION = 'prometheus.tolerations'
|
|
|
|
const SCHEDULABLE_COMPONENTS = ['prometheus', 'grafana', 'operator', 'exporter-kube-state']
|
|
|
|
export default Component.extend(ReservationCheck, CatalogUpgrade, {
|
|
scope: service(),
|
|
settings: service(),
|
|
|
|
layout,
|
|
|
|
templateId: MONITORING_TEMPLATE,
|
|
answers: null,
|
|
customAnswers: null,
|
|
level: 'cluster',
|
|
confirmDisable: false,
|
|
justDeployed: false,
|
|
enablePrometheusPersistence: false,
|
|
enableGrafanaPersistence: false,
|
|
enableNodeExporter: true,
|
|
prometheusPersistenceSize: '50Gi',
|
|
grafanaPersistenceSize: '10Gi',
|
|
requestsCpu: '750',
|
|
limitsCpu: '1000',
|
|
nodeExporterLimitsCpu: '200',
|
|
nodeExporterLimitsMemory: '200',
|
|
operatorLimitsMemory: '500',
|
|
requestsMemory: '750',
|
|
limitsMemory: '1000',
|
|
prometheusStorageClass: null,
|
|
grafanaStorageClass: null,
|
|
nodeSelectors: null,
|
|
retention: 12,
|
|
port: 9796,
|
|
projectLevelMinCpu: 500,
|
|
projectLevelMinMemory: 500,
|
|
|
|
cluster: alias('scope.currentCluster'),
|
|
project: alias('scope.currentProject'),
|
|
istioEnabled: alias('cluster.istioEnabled'),
|
|
|
|
actions: {
|
|
promptDisable() {
|
|
if (get(this, 'istioEnabled') && get(this, 'level') === 'cluster') {
|
|
return
|
|
}
|
|
|
|
set(this, 'confirmDisable', true);
|
|
later(this, function() {
|
|
if (this.isDestroyed || this.isDestroying) {
|
|
return;
|
|
}
|
|
set(this, 'confirmDisable', false);
|
|
}, 10000);
|
|
},
|
|
|
|
enable(cb) {
|
|
const resource = get(this, 'level') === 'cluster' ? get(this, 'cluster') : get(this, 'project');
|
|
|
|
let answers = get(this, 'level') === 'cluster' ? { ...CLUSTER_HIDDEN_KEYS } : {};
|
|
|
|
if ( get(this, 'level') === 'cluster' ) {
|
|
answers['exporter-node.enabled'] = `${ get(this, 'enableNodeExporter') }`;
|
|
answers['exporter-node.ports.metrics.port'] = `${ get(this, 'port') }`;
|
|
answers['exporter-kubelets.https'] = `${ !(get(this, 'scope.currentCluster.isGKE') || get(this, 'scope.currentCluster.isAKS')) }`;
|
|
answers['exporter-node.resources.limits.cpu'] = `${ get(this, 'nodeExporterLimitsCpu') }m`;
|
|
answers['exporter-node.resources.limits.memory'] = `${ get(this, 'nodeExporterLimitsMemory') }Mi`;
|
|
answers['operator.resources.limits.memory'] = `${ get(this, 'operatorLimitsMemory') }Mi`;
|
|
}
|
|
|
|
answers['prometheus.retention'] = `${ get(this, 'retention') }h`;
|
|
answers['grafana.persistence.enabled'] = `${ get(this, 'enableGrafanaPersistence') }`;
|
|
answers['prometheus.persistence.enabled'] = `${ get(this, 'enablePrometheusPersistence') }`;
|
|
answers['prometheus.persistence.storageClass'] = `${ get(this, 'prometheusStorageClass') === null ? 'default' : get(this, 'prometheusStorageClass') }`;
|
|
answers['grafana.persistence.storageClass'] = `${ get(this, 'grafanaStorageClass') === null ? 'default' : get(this, 'grafanaStorageClass') }`;
|
|
answers['grafana.persistence.size'] = `${ get(this, 'grafanaPersistenceSize') }`;
|
|
answers['prometheus.persistence.size'] = `${ get(this, 'prometheusPersistenceSize') }`;
|
|
answers['prometheus.resources.core.requests.cpu'] = `${ get(this, 'requestsCpu') }m`;
|
|
answers['prometheus.resources.core.limits.cpu'] = `${ get(this, 'limitsCpu') }m`;
|
|
answers['prometheus.resources.core.requests.memory'] = `${ get(this, 'requestsMemory') }Mi`;
|
|
answers['prometheus.resources.core.limits.memory'] = `${ get(this, 'limitsMemory') }Mi`;
|
|
if ( !get(this, 'enabled') || get(this, 'useReleaseName') ) {
|
|
answers['prometheus.persistent.useReleaseName'] = 'true';
|
|
}
|
|
|
|
Object.keys(answers).filter((key) => SCHEDULABLE_COMPONENTS.find((c) => key.startsWith(`${ c }.nodeSelectors[`))).forEach((key) => {
|
|
delete answers[key]
|
|
});
|
|
|
|
(get(this, 'nodeSelectors') || []).forEach((selector, index) => {
|
|
let s = get(selector, 'key');
|
|
let v = get(selector, 'value');
|
|
|
|
if ( v && v.startsWith('"') && v.endsWith('"') ) {
|
|
s += `="${ get(selector, 'value') }"`;
|
|
} else if (v) {
|
|
s += `="${ get(selector, 'value') }"`;
|
|
}
|
|
SCHEDULABLE_COMPONENTS.forEach((c) => {
|
|
answers[`${ c }.nodeSelectors[${ index }]`] = s;
|
|
});
|
|
});
|
|
|
|
Object.keys(answers).filter((key) => SCHEDULABLE_COMPONENTS.find((c) => key.startsWith(`${ c }.tolerations[`))).forEach((key) => {
|
|
delete answers[key]
|
|
});
|
|
|
|
SCHEDULABLE_COMPONENTS.map((component) => {
|
|
(get(this, `prometheusTolerations`) || []).map((t, index) => {
|
|
Object.keys(t).map((key) => {
|
|
if (t[key]) {
|
|
answers[`${ component }.tolerations[${ index }].${ key }`] = t[key].toString()
|
|
}
|
|
})
|
|
});
|
|
})
|
|
|
|
let action = get(this, 'enabled') ? 'editMonitoring' : 'enableMonitoring';
|
|
|
|
const customAnswers = get(this, 'customAnswers') || {};
|
|
|
|
Object.keys(customAnswers).forEach((key) => {
|
|
answers[key] = customAnswers[key]
|
|
});
|
|
|
|
const params = { answers };
|
|
|
|
if ( !get(this, 'enabled') && get(this, 'templateVersion') ) {
|
|
params['version'] = get(this, 'templateVersion');
|
|
}
|
|
|
|
resource.doAction(action, params).then(() => {
|
|
if (this.isDestroyed || this.isDestroying) {
|
|
return;
|
|
}
|
|
|
|
set(this, 'justDeployed', true);
|
|
|
|
if ( action === 'editMonitoring' ) {
|
|
this.send('upgrade');
|
|
}
|
|
cb(true);
|
|
this.fetchSettings();
|
|
}).catch(() => {
|
|
cb(false);
|
|
});
|
|
},
|
|
|
|
disable() {
|
|
const resource = get(this, 'level') === 'cluster' ? get(this, 'cluster') : get(this, 'project');
|
|
|
|
resource.doAction('disableMonitoring').then(() => {
|
|
if (this.isDestroyed || this.isDestroying) {
|
|
return;
|
|
}
|
|
|
|
setProperties(this, {
|
|
app: null,
|
|
answers: null,
|
|
customAnswers: null,
|
|
})
|
|
})
|
|
}
|
|
},
|
|
|
|
enabled: computed('cluster.enableClusterMonitoring', 'project.enableProjectMonitoring', 'level', function() {
|
|
return get(this, 'level') === 'cluster' ? get(this, 'cluster.enableClusterMonitoring') : get(this, 'project.enableProjectMonitoring');
|
|
}),
|
|
|
|
canDisableMonitor: computed('cluster.canDisableMonitor', 'project.canDisableMonitor', 'level', function() {
|
|
return get(this, 'level') === 'cluster' ? get(this, 'cluster.canDisableMonitor') : get(this, 'project.canDisableMonitor');
|
|
}),
|
|
|
|
canSaveMonitor: computed('cluster.canSaveMonitor', 'project.canSaveMonitor', 'level', function() {
|
|
return get(this, 'level') === 'cluster' ? get(this, 'cluster.canSaveMonitor') : get(this, 'project.canSaveMonitor');
|
|
}),
|
|
|
|
clusterLevelMinCpu: computed('cluster.enableClusterMonitoring', 'project.enableProjectMonitoring', 'level', function() {
|
|
const allNodes = get(this, 'scope.currentCluster.nodes') || [];
|
|
const schedulableNodes = allNodes.filterBy('isUnschedulable', false);
|
|
|
|
return CLUSTER_CPU + get(schedulableNodes, 'length') * NODE_EXPORTER_CPU;
|
|
}),
|
|
|
|
clusterLevelMinMemory: computed('cluster.enableClusterMonitoring', 'project.enableProjectMonitoring', 'level', function() {
|
|
const allNodes = get(this, 'scope.currentCluster.nodes') || [];
|
|
const schedulableNodes = allNodes.filterBy('isUnschedulable', false);
|
|
|
|
return CLUSTER_MEMORY + get(schedulableNodes, 'length') * NODE_EXPORTER_MEMORY;
|
|
}),
|
|
|
|
saveDisabled: computed('canSaveMonitor', 'insufficientPrometheusMemory', 'insufficientPrometheusCpu', 'insufficient', 'enabled', function() {
|
|
return !get(this, 'canSaveMonitor') || get(this, 'insufficient') || get(this, 'insufficientPrometheusCpu') || get(this, 'insufficientPrometheusMemory');
|
|
}),
|
|
|
|
insufficientPrometheusCpu: computed('schedulableNodes.@each.{allocatable,requested}', 'requestsCpu', 'scope.currentCluster.nodes.@each.{allocatable,requested}', function() {
|
|
const allNodes = get(this, 'scope.currentCluster.nodes') || [];
|
|
|
|
if ( get(allNodes, 'length') === 0 ) {
|
|
return false;
|
|
}
|
|
|
|
let maxLeftCpu = 0;
|
|
|
|
get(this, 'schedulableNodes').forEach((node) => {
|
|
const left = convertToMillis(get(node, 'allocatable.cpu')) - convertToMillis(get(node, 'requested.cpu') || '0');
|
|
|
|
if ( left > maxLeftCpu) {
|
|
maxLeftCpu = left;
|
|
}
|
|
});
|
|
|
|
return !get(this, 'enabled') && maxLeftCpu <= get(this, 'prometheusRequestCpu');
|
|
}),
|
|
|
|
prometheusRequestCpu: computed('requestsCpu', function() {
|
|
return parseInt(get(this, 'requestsCpu'), 10) + NODE_EXPORTER_CPU + 200;
|
|
}),
|
|
|
|
prometheusRequestMemory: computed('requestsMemory', function() {
|
|
return parseInt(get(this, 'requestsMemory'), 10) + NODE_EXPORTER_MEMORY + 200;
|
|
}),
|
|
|
|
insufficientPrometheusMemory: computed('schedulableNodes.@each.{allocatable,requested}', 'requestsMemory', 'scope.currentCluster.nodes.@each.{allocatable,requested}', function() {
|
|
const allNodes = get(this, 'scope.currentCluster.nodes') || [];
|
|
|
|
if ( get(allNodes, 'length') === 0 ) {
|
|
return false;
|
|
}
|
|
|
|
let maxLeftMemory = 0;
|
|
|
|
get(this, 'schedulableNodes').forEach((node) => {
|
|
const left = (parseSi(get(node, 'allocatable.memory'), 1024) / 1048576) - (parseSi(get(node, 'requested.memory') || '0', 1024) / 1048576);
|
|
|
|
if ( left > maxLeftMemory) {
|
|
maxLeftMemory = left;
|
|
}
|
|
});
|
|
|
|
return !get(this, 'enabled') && maxLeftMemory <= get(this, 'prometheusRequestMemory');
|
|
}),
|
|
|
|
schedulableNodes: computed('nodeSelectors', 'scope.currentCluster.nodes.@each.{allocatable,requested}', function() {
|
|
const allNodes = get(this, 'scope.currentCluster.nodes') || [];
|
|
const out = allNodes.filterBy('isUnschedulable', false)
|
|
.filterBy('osInfo', 'linux')
|
|
.filter((node) => (get(this, 'nodeSelectors') || [])
|
|
.every((selector) => {
|
|
const labelValue = (get(node, 'labels') || {})[get(selector, 'key')];
|
|
|
|
if ( get(selector, 'value') === '' ) {
|
|
return labelValue !== undefined;
|
|
} else {
|
|
return get(selector, 'value') === labelValue;
|
|
}
|
|
}));
|
|
|
|
return out;
|
|
}),
|
|
|
|
initSettings: on('init', observer('scope.currentProject.id', 'scope.currentCluster.id', function() {
|
|
if ( get(this, 'enabled') ) {
|
|
this.fetchSettings();
|
|
}
|
|
})),
|
|
|
|
fetchSettings() {
|
|
const resource = get(this, 'level') === 'cluster' ? get(this, 'cluster') : get(this, 'project');
|
|
|
|
if ( !resource ) {
|
|
return;
|
|
}
|
|
|
|
set(this, 'loading', true);
|
|
resource.waitForAction('viewMonitoring').then(() => {
|
|
if (this.isDestroyed || this.isDestroying) {
|
|
return;
|
|
}
|
|
|
|
resource.doAction('viewMonitoring').then((res) => {
|
|
if (this.isDestroyed || this.isDestroying) {
|
|
return;
|
|
}
|
|
|
|
const body = get(res, 'answers');
|
|
const answers = {};
|
|
const customAnswers = {};
|
|
|
|
Object.keys(body || {}).forEach((key) => {
|
|
if ( EXPOSED_OPTIONS.indexOf(key) > -1 ||
|
|
SCHEDULABLE_COMPONENTS.find((c) => key.startsWith(`${ c }.nodeSelectors[`)) ||
|
|
SCHEDULABLE_COMPONENTS.find((c) => key.startsWith(`${ c }.tolerations[`))
|
|
) {
|
|
answers[key] = body[key];
|
|
} else if (!Object.keys(CLUSTER_HIDDEN_KEYS).includes(key)) {
|
|
customAnswers[key] = body[key];
|
|
}
|
|
});
|
|
|
|
set(this, 'answers', answers);
|
|
set(this, 'customAnswers', customAnswers);
|
|
this.updateConfig(answers);
|
|
set(this, 'loading', false);
|
|
}).catch(() => {
|
|
set(this, 'loading', false);
|
|
});
|
|
})
|
|
},
|
|
|
|
updateConfig(answers) {
|
|
if ( answers['prometheus.persistent.useReleaseName'] ) {
|
|
set(this, 'useReleaseName', answers['prometheus.persistent.useReleaseName']);
|
|
} else {
|
|
set(this, 'useReleaseName', null);
|
|
}
|
|
|
|
if ( answers['prometheus.resources.core.requests.cpu'] ) {
|
|
const requestsCpu = convertToMillis(answers['prometheus.resources.core.requests.cpu'])
|
|
|
|
setProperties(this, {
|
|
requestsCpu,
|
|
preRequestsCpu: requestsCpu,
|
|
})
|
|
}
|
|
|
|
if ( answers['prometheus.resources.core.limits.cpu'] ) {
|
|
set(this, 'limitsCpu', convertToMillis(answers['prometheus.resources.core.limits.cpu']));
|
|
}
|
|
|
|
if ( answers['exporter-node.resources.limits.cpu'] ) {
|
|
set(this, 'nodeExporterLimitsCpu', convertToMillis(answers['exporter-node.resources.limits.cpu']));
|
|
}
|
|
|
|
if ( answers['prometheus.resources.core.requests.memory'] ) {
|
|
const requestsMemory = parseSi(answers['prometheus.resources.core.requests.memory'], 1024) / 1048576
|
|
|
|
setProperties(this, {
|
|
requestsMemory,
|
|
preRequestsMemory: requestsMemory,
|
|
})
|
|
}
|
|
|
|
if ( answers['prometheus.resources.core.limits.memory'] ) {
|
|
set(this, 'limitsMemory', parseSi(answers['prometheus.resources.core.limits.memory'], 1024) / 1048576);
|
|
}
|
|
|
|
if ( answers['exporter-node.resources.limits.memory'] ) {
|
|
set(this, 'nodeExporterLimitsMemory', parseSi(answers['exporter-node.resources.limits.memory'], 1024) / 1048576);
|
|
} else {
|
|
set(this, 'nodeExporterLimitsMemory', '50');
|
|
}
|
|
|
|
if ( answers['operator.resources.limits.memory'] ) {
|
|
set(this, 'operatorLimitsMemory', parseSi(answers['operator.resources.limits.memory'], 1024) / 1048576);
|
|
} else {
|
|
set(this, 'operatorLimitsMemory', '100');
|
|
}
|
|
|
|
if ( answers['prometheus.retention'] ) {
|
|
set(this, 'retention', answers['prometheus.retention'].substr(0, answers['prometheus.retention'].length - 1));
|
|
}
|
|
if ( answers['grafana.persistence.enabled'] ) {
|
|
set(this, 'enableGrafanaPersistence', answers['grafana.persistence.enabled'] === 'true');
|
|
}
|
|
if ( answers['prometheus.persistence.enabled'] ) {
|
|
set(this, 'enablePrometheusPersistence', answers['prometheus.persistence.enabled'] === 'true');
|
|
}
|
|
if ( answers['prometheus.persistence.storageClass'] ) {
|
|
set(this, 'prometheusStorageClass', answers['prometheus.persistence.storageClass'] === 'default' ? null : answers['prometheus.persistence.storageClass']);
|
|
}
|
|
if ( answers['grafana.persistence.storageClass'] ) {
|
|
set(this, 'grafanaStorageClass', answers['grafana.persistence.storageClass'] === 'default' ? null : answers['grafana.persistence.storageClass']);
|
|
}
|
|
if ( answers['grafana.persistence.size'] ) {
|
|
set(this, 'grafanaPersistenceSize', answers['grafana.persistence.size'])
|
|
}
|
|
if ( answers['prometheus.persistence.size'] ) {
|
|
set(this, 'prometheusPersistenceSize', answers['prometheus.persistence.size'])
|
|
}
|
|
if ( get(this, 'level') === 'cluster' ) {
|
|
if ( answers['exporter-node.enabled'] ) {
|
|
set(this, 'enableNodeExporter', answers['exporter-node.enabled'] === 'true');
|
|
}
|
|
if ( answers['exporter-node.ports.metrics.port'] ) {
|
|
set(this, 'port', answers['exporter-node.ports.metrics.port'])
|
|
}
|
|
}
|
|
let nodeSelectorsStr = '';
|
|
|
|
Object.keys(answers).filter((key) => key.startsWith('prometheus.nodeSelectors[') ).forEach((key) => {
|
|
let value = answers[key];
|
|
|
|
if ( value ) {
|
|
const index = value.indexOf('=');
|
|
|
|
if ( index > -1 ) {
|
|
let keyStr = value.slice(index + 1);
|
|
|
|
if ( keyStr && keyStr.startsWith('"') && keyStr.endsWith('"') ) {
|
|
keyStr = keyStr.slice(1, keyStr.length - 1);
|
|
}
|
|
|
|
value = `${ value.slice(0, index) }=${ keyStr }`;
|
|
}
|
|
}
|
|
nodeSelectorsStr += `${ value },`;
|
|
});
|
|
|
|
set(this, 'nodeSelectorsStr', nodeSelectorsStr);
|
|
|
|
const prometheusTolerations = []
|
|
|
|
const prometheusTolerationKeys = Object.keys(answers).filter((key) => key.startsWith(PROMETHEUS_TOLERATION) )
|
|
const prometheusTolerationIndexs = prometheusTolerationKeys.map((k) => {
|
|
return k.replace(`${ PROMETHEUS_TOLERATION }[`, '').split('].').get('firstObject')
|
|
}).uniq()
|
|
|
|
prometheusTolerationIndexs.map((idx) => {
|
|
prometheusTolerations.pushObject({
|
|
key: answers[`${ PROMETHEUS_TOLERATION }[${ idx }].key`] || '',
|
|
operator: answers[`${ PROMETHEUS_TOLERATION }[${ idx }].operator`] || '',
|
|
value: answers[`${ PROMETHEUS_TOLERATION }[${ idx }].value`] || '',
|
|
effect: answers[`${ PROMETHEUS_TOLERATION }[${ idx }].effect`] || '',
|
|
tolerationSeconds: answers[`${ PROMETHEUS_TOLERATION }[${ idx }].tolerationSeconds`] || '',
|
|
})
|
|
})
|
|
set(this, 'prometheusTolerations', prometheusTolerations)
|
|
},
|
|
});
|