1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-23 15:29:42 +02:00

feat(k8s/application): add/edit placement preferences/constraints (#4210)

* feat(k8s/application): create application with placement preferences/constraints

* feat(k8s/application): edit application placement preferences/constraints
This commit is contained in:
xAt0mZ 2020-08-14 01:56:53 +02:00 committed by GitHub
parent 32bac9ffcc
commit 52bdcf2e2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 451 additions and 190 deletions

View file

@ -1,4 +1,4 @@
import _ from 'lodash-es';
import * as _ from 'lodash-es';
import { KubernetesPortMapping, KubernetesPortMappingPort } from 'Kubernetes/models/port/models';
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
@ -10,6 +10,7 @@ import {
KubernetesApplicationPersistedFolderFormValue,
KubernetesApplicationPublishedPortFormValue,
KubernetesApplicationAutoScalerFormValue,
KubernetesApplicationPlacementFormValue,
} from 'Kubernetes/models/application/formValues';
import {
KubernetesApplicationEnvConfigMapPayload,
@ -22,8 +23,21 @@ import {
KubernetesApplicationVolumeSecretPayload,
} from 'Kubernetes/models/application/payloads';
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
import { KubernetesApplicationPlacementTypes, KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models';
import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators, KubernetesPodAffinity } from 'Kubernetes/pod/models';
import {
KubernetesNodeSelectorTermPayload,
KubernetesPreferredSchedulingTermPayload,
KubernetesPodNodeAffinityPayload,
KubernetesNodeSelectorRequirementPayload,
} from 'Kubernetes/pod/payloads/affinities';
class KubernetesApplicationHelper {
/* #region UTILITY FUNCTIONS */
static isExternalApplication(application) {
return !application.ApplicationOwner;
}
static associatePodsAndApplication(pods, app) {
return _.filter(pods, { Labels: app.spec.selector.matchLabels });
}
@ -93,10 +107,9 @@ class KubernetesApplicationHelper {
);
return res;
}
/* #endregion */
/**
* FORMVALUES TO APPLICATION FUNCTIONS
*/
/* #region ENV VARIABLES FV <> ENV */
static generateEnvFromEnvVariables(envVariables) {
_.remove(envVariables, (item) => item.NeedsDeletion);
const env = _.map(envVariables, (item) => {
@ -108,6 +121,75 @@ class KubernetesApplicationHelper {
return env;
}
static generateEnvVariablesFromEnv(env) {
const envVariables = _.map(env, (item) => {
if (!item.value) {
return;
}
const res = new KubernetesApplicationEnvironmentVariableFormValue();
res.Name = item.name;
res.Value = item.value;
res.IsNew = false;
return res;
});
return _.without(envVariables, undefined);
}
/* #endregion */
/* #region CONFIGURATIONS FV <> ENV & VOLUMES */
static generateConfigurationFormValuesFromEnvAndVolumes(env, volumes, configurations) {
const finalRes = _.flatMap(configurations, (cfg) => {
const filterCondition = cfg.Type === KubernetesConfigurationTypes.CONFIGMAP ? 'valueFrom.configMapKeyRef.name' : 'valueFrom.secretKeyRef.name';
const cfgEnv = _.filter(env, [filterCondition, cfg.Name]);
const cfgVol = _.filter(volumes, { configurationName: cfg.Name });
if (!cfgEnv.length && !cfgVol.length) {
return;
}
const keys = _.reduce(
_.keys(cfg.Data),
(acc, k) => {
const keyEnv = _.filter(cfgEnv, { name: k });
const keyVol = _.filter(cfgVol, { configurationKey: k });
const key = {
Key: k,
Count: keyEnv.length + keyVol.length,
Sum: _.concat(keyEnv, keyVol),
EnvCount: keyEnv.length,
VolCount: keyVol.length,
};
acc.push(key);
return acc;
},
[]
);
const max = _.max(_.map(keys, 'Count'));
const overrideThreshold = max - _.max(_.map(keys, 'VolCount'));
const res = _.map(new Array(max), () => new KubernetesApplicationConfigurationFormValue());
_.forEach(res, (item, index) => {
item.SelectedConfiguration = cfg;
const overriden = index >= overrideThreshold;
if (overriden) {
item.Overriden = true;
item.OverridenKeys = _.map(keys, (k) => {
const fvKey = new KubernetesApplicationConfigurationFormValueOverridenKey();
fvKey.Key = k.Key;
if (index < k.EnvCount) {
fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT;
} else {
fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM;
fvKey.Path = k.Sum[index].rootMountPath;
}
return fvKey;
});
}
});
return res;
});
return _.without(finalRes, undefined);
}
static generateEnvOrVolumesFromConfigurations(app, configurations) {
let finalEnv = [];
let finalVolumes = [];
@ -181,110 +263,9 @@ class KubernetesApplicationHelper {
app.VolumeMounts = _.concat(app.VolumeMounts, finalMounts);
return app;
}
/* #endregion */
static generateVolumesFromPersistentVolumClaims(app, volumeClaims) {
app.VolumeMounts = [];
app.Volumes = [];
_.forEach(volumeClaims, (item) => {
const volumeMount = new KubernetesApplicationVolumeMountPayload();
const name = item.Name;
volumeMount.name = name;
volumeMount.mountPath = item.MountPath;
app.VolumeMounts.push(volumeMount);
const volume = new KubernetesApplicationVolumePersistentPayload();
volume.name = name;
volume.persistentVolumeClaim.claimName = name;
app.Volumes.push(volume);
});
}
/**
* !FORMVALUES TO APPLICATION FUNCTIONS
*/
/**
* APPLICATION TO FORMVALUES FUNCTIONS
*/
static generateEnvVariablesFromEnv(env) {
const envVariables = _.map(env, (item) => {
if (!item.value) {
return;
}
const res = new KubernetesApplicationEnvironmentVariableFormValue();
res.Name = item.name;
res.Value = item.value;
res.IsNew = false;
return res;
});
return _.without(envVariables, undefined);
}
static generateConfigurationFormValuesFromEnvAndVolumes(env, volumes, configurations) {
const finalRes = _.flatMap(configurations, (cfg) => {
const filterCondition = cfg.Type === KubernetesConfigurationTypes.CONFIGMAP ? 'valueFrom.configMapKeyRef.name' : 'valueFrom.secretKeyRef.name';
const cfgEnv = _.filter(env, [filterCondition, cfg.Name]);
const cfgVol = _.filter(volumes, { configurationName: cfg.Name });
if (!cfgEnv.length && !cfgVol.length) {
return;
}
const keys = _.reduce(
_.keys(cfg.Data),
(acc, k) => {
const keyEnv = _.filter(cfgEnv, { name: k });
const keyVol = _.filter(cfgVol, { configurationKey: k });
const key = {
Key: k,
Count: keyEnv.length + keyVol.length,
Sum: _.concat(keyEnv, keyVol),
EnvCount: keyEnv.length,
VolCount: keyVol.length,
};
acc.push(key);
return acc;
},
[]
);
const max = _.max(_.map(keys, 'Count'));
const overrideThreshold = max - _.max(_.map(keys, 'VolCount'));
const res = _.map(new Array(max), () => new KubernetesApplicationConfigurationFormValue());
_.forEach(res, (item, index) => {
item.SelectedConfiguration = cfg;
const overriden = index >= overrideThreshold;
if (overriden) {
item.Overriden = true;
item.OverridenKeys = _.map(keys, (k) => {
const fvKey = new KubernetesApplicationConfigurationFormValueOverridenKey();
fvKey.Key = k.Key;
if (index < k.EnvCount) {
fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT;
} else {
fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM;
fvKey.Path = k.Sum[index].rootMountPath;
}
return fvKey;
});
}
});
return res;
});
return _.without(finalRes, undefined);
}
static generatePersistedFoldersFormValuesFromPersistedFolders(persistedFolders, persistentVolumeClaims) {
const finalRes = _.map(persistedFolders, (folder) => {
const pvc = _.find(persistentVolumeClaims, (item) => _.startsWith(item.Name, folder.PersistentVolumeClaimName));
const res = new KubernetesApplicationPersistedFolderFormValue(pvc.StorageClass);
res.PersistentVolumeClaimName = folder.PersistentVolumeClaimName;
res.Size = parseInt(pvc.Storage.slice(0, -2));
res.SizeUnit = pvc.Storage.slice(-2);
res.ContainerPath = folder.MountPath;
return res;
});
return finalRes;
}
/* #region PUBLISHED PORTS FV <> PUBLISHED PORTS */
static generatePublishedPortsFormValuesFromPublishedPorts(serviceType, publishedPorts) {
const generatePort = (port, rule) => {
const res = new KubernetesApplicationPublishedPortFormValue();
@ -313,7 +294,9 @@ class KubernetesApplicationHelper {
});
return finalRes;
}
/* #endregion */
/* #region AUTOSCALER FV <> HORIZONTAL POD AUTOSCALER */
static generateAutoScalerFormValueFromHorizontalPodAutoScaler(autoScaler, replicasCount) {
const res = new KubernetesApplicationAutoScalerFormValue();
if (autoScaler) {
@ -330,12 +313,111 @@ class KubernetesApplicationHelper {
return res;
}
/**
* !APPLICATION TO FORMVALUES FUNCTIONS
*/
/* #endregion */
static isExternalApplication(application) {
return !application.ApplicationOwner;
/* #region PERSISTED FOLDERS FV <> VOLUMES */
static generatePersistedFoldersFormValuesFromPersistedFolders(persistedFolders, persistentVolumeClaims) {
const finalRes = _.map(persistedFolders, (folder) => {
const pvc = _.find(persistentVolumeClaims, (item) => _.startsWith(item.Name, folder.PersistentVolumeClaimName));
const res = new KubernetesApplicationPersistedFolderFormValue(pvc.StorageClass);
res.PersistentVolumeClaimName = folder.PersistentVolumeClaimName;
res.Size = parseInt(pvc.Storage.slice(0, -2));
res.SizeUnit = pvc.Storage.slice(-2);
res.ContainerPath = folder.MountPath;
return res;
});
return finalRes;
}
static generateVolumesFromPersistentVolumClaims(app, volumeClaims) {
app.VolumeMounts = [];
app.Volumes = [];
_.forEach(volumeClaims, (item) => {
const volumeMount = new KubernetesApplicationVolumeMountPayload();
const name = item.Name;
volumeMount.name = name;
volumeMount.mountPath = item.MountPath;
app.VolumeMounts.push(volumeMount);
const volume = new KubernetesApplicationVolumePersistentPayload();
volume.name = name;
volume.persistentVolumeClaim.claimName = name;
app.Volumes.push(volume);
});
}
/* #endregion */
/* #region PLACEMENTS FV <> AFFINITY */
static generatePlacementsFormValuesFromAffinity(formValues, podAffinity, nodesLabels) {
let placements = formValues.Placements;
let type = formValues.PlacementType;
const affinity = podAffinity.nodeAffinity;
if (affinity && affinity.requiredDuringSchedulingIgnoredDuringExecution) {
type = KubernetesApplicationPlacementTypes.MANDATORY;
_.forEach(affinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms, (term) => {
_.forEach(term.matchExpressions, (exp) => {
const placement = new KubernetesApplicationPlacementFormValue();
const label = _.find(nodesLabels, { Key: exp.key });
placement.Label = label;
placement.Value = exp.values[0];
placement.IsNew = false;
placements.push(placement);
});
});
} else if (affinity && affinity.preferredDuringSchedulingIgnoredDuringExecution) {
type = KubernetesApplicationPlacementTypes.PREFERRED;
_.forEach(affinity.preferredDuringSchedulingIgnoredDuringExecution, (term) => {
_.forEach(term.preference.matchExpressions, (exp) => {
const placement = new KubernetesApplicationPlacementFormValue();
const label = _.find(nodesLabels, { Key: exp.key });
placement.Label = label;
placement.Value = exp.values[0];
placement.IsNew = false;
placements.push(placement);
});
});
}
formValues.Placements = placements;
formValues.PlacementType = type;
}
static generateAffinityFromPlacements(app, formValues) {
if (formValues.DeploymentType === KubernetesApplicationDeploymentTypes.REPLICATED) {
const placements = formValues.Placements;
const res = new KubernetesPodNodeAffinityPayload();
let expressions = _.map(placements, (p) => {
if (!p.NeedsDeletion) {
const exp = new KubernetesNodeSelectorRequirementPayload();
exp.key = p.Label.Key;
if (p.Value) {
exp.operator = KubernetesPodNodeAffinityNodeSelectorRequirementOperators.IN;
exp.values = [p.Value];
} else {
exp.operator = KubernetesPodNodeAffinityNodeSelectorRequirementOperators.EXISTS;
delete exp.values;
}
return exp;
}
});
expressions = _.without(expressions, undefined);
if (expressions.length) {
if (formValues.PlacementType === KubernetesApplicationPlacementTypes.MANDATORY) {
const term = new KubernetesNodeSelectorTermPayload();
term.matchExpressions = expressions;
res.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.push(term);
delete res.preferredDuringSchedulingIgnoredDuringExecution;
} else if (formValues.PlacementType === KubernetesApplicationPlacementTypes.PREFERRED) {
const term = new KubernetesPreferredSchedulingTermPayload();
term.preference = new KubernetesNodeSelectorTermPayload();
term.preference.matchExpressions = expressions;
res.preferredDuringSchedulingIgnoredDuringExecution.push(term);
delete res.requiredDuringSchedulingIgnoredDuringExecution;
}
app.Affinity = new KubernetesPodAffinity();
app.Affinity.nodeAffinity = res;
}
}
}
/* #endregion */
}
export default KubernetesApplicationHelper;