dashboard/shell/models/cluster.x-k8s.io.machinedep...

238 lines
6.8 KiB
JavaScript

import { CAPI } from '@shell/config/types';
import { escapeHtml } from '@shell/utils/string';
import { sortBy } from '@shell/utils/sort';
import SteveModel from '@shell/plugins/steve/steve-class';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { handleConflict } from '@shell/plugins/dashboard-store/normalize';
import { MACHINE_ROLES } from '@shell/config/labels-annotations';
import { notOnlyOfRole } from '@shell/models/cluster.x-k8s.io.machine';
import { KIND } from '../config/elemental-types';
import { KIND as HARVESTER_KIND } from '../config/harvester-manager-types';
export default class CapiMachineDeployment extends SteveModel {
get cluster() {
if ( !this.spec.clusterName ) {
return null;
}
const clusterId = `${ this.metadata.namespace }/${ this.spec.clusterName }`;
const cluster = this.$rootGetters['management/byId'](CAPI.RANCHER_CLUSTER, clusterId);
return cluster;
}
get groupByLabel() {
const name = this.cluster?.nameDisplay || this.spec.clusterName;
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.cluster', { name: escapeHtml(name) });
}
get groupByPoolLabel() {
return `${ this.$rootGetters['i18n/t']('resourceTable.groupLabel.machinePool', { name: escapeHtml(this.nameDisplay) }) }`;
}
get groupByPoolShortLabel() {
return `${ this.$rootGetters['i18n/t']('resourceTable.groupLabel.machinePool', { name: escapeHtml(this.nameDisplay) }) }`;
}
get infrastructureRefKind() {
return this.spec?.template?.spec?.infrastructureRef?.kind;
}
get templateType() {
return this.spec.template.spec.infrastructureRef.kind ? `rke-machine.cattle.io.${ this.spec.template.spec.infrastructureRef.kind.toLowerCase() }` : null;
}
get template() {
const ref = this.spec.template.spec.infrastructureRef;
const id = `${ ref.namespace }/${ ref.name }`;
const template = this.$rootGetters['management/byId'](this.templateType, id);
return template;
}
get providerName() {
return this.template?.nameDisplay;
}
get providerDisplay() {
const provider = (this.template?.provider || '').toLowerCase();
return this.$rootGetters['i18n/withFallback'](`cluster.provider."${ provider }"`, null, 'generic.unknown', true);
}
get providerLocation() {
return this.template?.providerLocation || this.t('node.list.poolDescription.noLocation');
}
get providerSize() {
return this.template?.providerSize || this.t('node.list.poolDescription.noSize');
}
get providerSummary() {
if (this.template) {
switch (this.infrastructureRefKind) {
case HARVESTER_KIND.MACHINE_TEMPLATE:
return null;
default:
return `${ this.providerDisplay } \u2013 ${ this.providerLocation } / ${ this.providerSize } (${ this.providerName })`;
}
}
return null;
}
get desired() {
return this.spec?.replicas || 0;
}
get pending() {
return Math.max(0, this.desired - (this.status?.replicas || 0));
}
get outdated() {
return Math.max(0, (this.status?.replicas || 0) - (this.status?.updatedReplicas || 0));
}
get ready() {
return Math.max(0, (this.status?.replicas || 0) - (this.status?.unavailableReplicas || 0));
}
get unavailable() {
return this.status?.unavailableReplicas || 0;
}
get isControlPlane() {
return `${ this.spec?.template?.metadata?.labels?.[MACHINE_ROLES.CONTROL_PLANE] }` === 'true';
}
get isEtcd() {
return `${ this.spec?.template?.metadata?.labels?.[MACHINE_ROLES.ETCD] }` === 'true';
}
// use this pool's definition in the cluster's rkeConfig to scale, not this.spec.replicas
get inClusterSpec() {
const machineConfigName = this.template?.metadata?.annotations['rke.cattle.io/cloned-from-name'];
const machinePools = this.cluster.spec.rkeConfig.machinePools;
return machinePools.find((pool) => pool.machineConfigRef.name === machineConfigName);
}
scalePool(delta, save = true, depth = 0) {
// This is used in different places with different scaling rules, so don't check if we can/cannot scale
if (!this.inClusterSpec) {
return;
}
const initialValue = this.cluster;
this.inClusterSpec.quantity += delta;
if ( !save ) {
return;
}
const value = this.cluster;
const liveModel = this.$rootGetters['management/byId'](CAPI.RANCHER_CLUSTER, this.cluster.id);
if ( this.scaleTimer ) {
clearTimeout(this.scaleTimer);
}
this.scaleTimer = setTimeout(() => {
this.cluster.save().catch(async(err) => {
let errors = exceptionToErrorsArray(err);
if ( err.status === 409 && depth < 2 ) {
const conflicts = await handleConflict(
initialValue,
value,
liveModel,
{
dispatch: this.$dispatch,
getters: this.$rootGetters
},
'management'
);
if ( conflicts === false ) {
// It was automatically figured out, save again
// (pass in the delta again as `this.inClusterSpec.quantity` would have reset from the re-fetch done in `save`)
return this.scalePool(delta, true, depth + 1);
} else {
errors = conflicts;
}
}
this.$dispatch('growl/fromError', {
title: 'Error scaling pool',
err: errors
}, { root: true });
});
}, 1000);
}
// prevent scaling pool to 0 if it would scale down the only etcd or control plane node
canScaleDownPool() {
if (!this.canUpdate || this.inClusterSpec?.quantity === 0 || this.infrastructureRefKind === KIND.MACHINE_INV_SELECTOR_TEMPLATES) {
return false;
}
// scaling workers is always ok
if (!this.isEtcd && !this.isControlPlane) {
return true;
}
return notOnlyOfRole(this, this.cluster.machines);
}
// prevent scaling up pool for Elemental machines
canScaleUpPool() {
if (this.infrastructureRefKind === KIND.MACHINE_INV_SELECTOR_TEMPLATES) {
return false;
}
return true;
}
get showScalePool() {
return this.canScaleDownPool() || this.canScaleUpPool();
}
get stateParts() {
const out = [
{
label: 'Pending',
color: 'bg-info',
textColor: 'text-info',
value: this.pending,
sort: 1,
},
{
label: 'Outdated',
color: 'bg-warning',
textColor: 'text-warning',
value: this.outdated,
sort: 2,
},
{
label: 'Unavailable',
color: 'bg-error',
textColor: 'text-error',
value: this.unavailable,
sort: 3,
},
{
label: 'Ready',
color: 'bg-success',
textColor: 'text-success',
value: this.ready,
sort: 4,
},
].filter((x) => x.value > 0);
return sortBy(out, 'sort:desc');
}
}