mirror of https://github.com/portainer/portainer
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/constraintspull/4214/head
parent
32bac9ffcc
commit
52bdcf2e2b
|
@ -49,7 +49,7 @@ function _apiPortsToPublishedPorts(pList, pRefs) {
|
|||
}
|
||||
|
||||
class KubernetesApplicationConverter {
|
||||
static applicationCommon(res, data, service, ingresses) {
|
||||
static applicationCommon(res, data, pods, service, ingresses) {
|
||||
const containers = _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined);
|
||||
res.Id = data.metadata.uid;
|
||||
res.Name = data.metadata.name;
|
||||
|
@ -61,6 +61,7 @@ class KubernetesApplicationConverter {
|
|||
res.Image = containers[0].image;
|
||||
res.CreationDate = data.metadata.creationTimestamp;
|
||||
res.Env = _.without(_.flatMap(_.map(containers, 'env')), undefined);
|
||||
res.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, data);
|
||||
|
||||
const limits = {
|
||||
Cpu: 0,
|
||||
|
@ -212,9 +213,9 @@ class KubernetesApplicationConverter {
|
|||
);
|
||||
}
|
||||
|
||||
static apiDeploymentToApplication(data, service, ingresses) {
|
||||
static apiDeploymentToApplication(data, pods, service, ingresses) {
|
||||
const res = new KubernetesApplication();
|
||||
KubernetesApplicationConverter.applicationCommon(res, data, service, ingresses);
|
||||
KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
|
||||
res.ApplicationType = KubernetesApplicationTypes.DEPLOYMENT;
|
||||
res.DeploymentType = KubernetesApplicationDeploymentTypes.REPLICATED;
|
||||
res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.SHARED;
|
||||
|
@ -223,9 +224,9 @@ class KubernetesApplicationConverter {
|
|||
return res;
|
||||
}
|
||||
|
||||
static apiDaemonSetToApplication(data, service, ingresses) {
|
||||
static apiDaemonSetToApplication(data, pods, service, ingresses) {
|
||||
const res = new KubernetesApplication();
|
||||
KubernetesApplicationConverter.applicationCommon(res, data, service, ingresses);
|
||||
KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
|
||||
res.ApplicationType = KubernetesApplicationTypes.DAEMONSET;
|
||||
res.DeploymentType = KubernetesApplicationDeploymentTypes.GLOBAL;
|
||||
res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.SHARED;
|
||||
|
@ -234,9 +235,9 @@ class KubernetesApplicationConverter {
|
|||
return res;
|
||||
}
|
||||
|
||||
static apiStatefulSetToapplication(data, service, ingresses) {
|
||||
static apiStatefulSetToapplication(data, pods, service, ingresses) {
|
||||
const res = new KubernetesApplication();
|
||||
KubernetesApplicationConverter.applicationCommon(res, data, service, ingresses);
|
||||
KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
|
||||
res.ApplicationType = KubernetesApplicationTypes.STATEFULSET;
|
||||
res.DeploymentType = KubernetesApplicationDeploymentTypes.REPLICATED;
|
||||
res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.ISOLATED;
|
||||
|
@ -246,7 +247,7 @@ class KubernetesApplicationConverter {
|
|||
return res;
|
||||
}
|
||||
|
||||
static applicationToFormValues(app, resourcePools, configurations, persistentVolumeClaims) {
|
||||
static applicationToFormValues(app, resourcePools, configurations, persistentVolumeClaims, nodesLabels) {
|
||||
const res = new KubernetesApplicationFormValues();
|
||||
res.ApplicationType = app.ApplicationType;
|
||||
res.ResourcePool = _.find(resourcePools, ['Namespace.Name', app.ResourcePool]);
|
||||
|
@ -276,6 +277,8 @@ class KubernetesApplicationConverter {
|
|||
} else {
|
||||
res.PublishingType = KubernetesApplicationPublishingTypes.INTERNAL;
|
||||
}
|
||||
|
||||
KubernetesApplicationHelper.generatePlacementsFormValuesFromAffinity(res, app.Pods[0].Affinity, nodesLabels);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ class KubernetesDaemonSetConverter {
|
|||
res.Env = KubernetesApplicationHelper.generateEnvFromEnvVariables(formValues.EnvironmentVariables);
|
||||
KubernetesApplicationHelper.generateVolumesFromPersistentVolumClaims(res, volumeClaims);
|
||||
KubernetesApplicationHelper.generateEnvOrVolumesFromConfigurations(res, formValues.Configurations);
|
||||
KubernetesApplicationHelper.generateAffinityFromPlacements(res, formValues);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -51,6 +52,7 @@ class KubernetesDaemonSetConverter {
|
|||
payload.spec.template.metadata.labels[KubernetesPortainerApplicationNameLabel] = daemonSet.ApplicationName;
|
||||
payload.spec.template.spec.containers[0].name = daemonSet.Name;
|
||||
payload.spec.template.spec.containers[0].image = daemonSet.Image;
|
||||
payload.spec.template.spec.affinity = daemonSet.Affinity;
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.containers[0].env', daemonSet.Env);
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.containers[0].volumeMounts', daemonSet.VolumeMounts);
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.volumes', daemonSet.Volumes);
|
||||
|
|
|
@ -32,6 +32,7 @@ class KubernetesDeploymentConverter {
|
|||
res.Containers = formValues.Containers;
|
||||
KubernetesApplicationHelper.generateVolumesFromPersistentVolumClaims(res, volumeClaims);
|
||||
KubernetesApplicationHelper.generateEnvOrVolumesFromConfigurations(res, formValues.Configurations);
|
||||
KubernetesApplicationHelper.generateAffinityFromPlacements(res, formValues);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -53,6 +54,7 @@ class KubernetesDeploymentConverter {
|
|||
payload.spec.template.metadata.labels[KubernetesPortainerApplicationNameLabel] = deployment.ApplicationName;
|
||||
payload.spec.template.spec.containers[0].name = deployment.Name;
|
||||
payload.spec.template.spec.containers[0].image = deployment.Image;
|
||||
payload.spec.template.spec.affinity = deployment.Affinity;
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.containers[0].env', deployment.Env);
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.containers[0].volumeMounts', deployment.VolumeMounts);
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.volumes', deployment.Volumes);
|
||||
|
|
|
@ -33,6 +33,7 @@ class KubernetesStatefulSetConverter {
|
|||
res.Env = KubernetesApplicationHelper.generateEnvFromEnvVariables(formValues.EnvironmentVariables);
|
||||
KubernetesApplicationHelper.generateVolumesFromPersistentVolumClaims(res, volumeClaims);
|
||||
KubernetesApplicationHelper.generateEnvOrVolumesFromConfigurations(res, formValues.Configurations);
|
||||
KubernetesApplicationHelper.generateAffinityFromPlacements(res, formValues);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -56,6 +57,7 @@ class KubernetesStatefulSetConverter {
|
|||
payload.spec.template.metadata.labels[KubernetesPortainerApplicationNameLabel] = statefulSet.ApplicationName;
|
||||
payload.spec.template.spec.containers[0].name = statefulSet.Name;
|
||||
payload.spec.template.spec.containers[0].image = statefulSet.Image;
|
||||
payload.spec.template.spec.affinity = statefulSet.Affinity;
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.containers[0].env', statefulSet.Env);
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.containers[0].volumeMounts', statefulSet.VolumeMounts);
|
||||
KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.volumes', statefulSet.Volumes);
|
||||
|
|
|
@ -119,4 +119,15 @@ angular
|
|||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('kubernetesNodeLabelHumanReadbleText', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
const values = {
|
||||
'kubernetes.io/os': 'Operating system',
|
||||
'kubernetes.io/arch': 'Architecture',
|
||||
'kubernetes.io/hostname': 'Node',
|
||||
};
|
||||
return values[text] || text;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes, KubernetesApplicationPublishingTypes } from './models';
|
||||
import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes, KubernetesApplicationPublishingTypes, KubernetesApplicationPlacementTypes } from './models';
|
||||
|
||||
/**
|
||||
* KubernetesApplicationFormValues Model
|
||||
|
@ -10,19 +10,21 @@ const _KubernetesApplicationFormValues = Object.freeze({
|
|||
StackName: '',
|
||||
ApplicationOwner: '',
|
||||
Image: '',
|
||||
ReplicaCount: 1,
|
||||
Note: '',
|
||||
EnvironmentVariables: [], // KubernetesApplicationEnvironmentVariableFormValue list
|
||||
PersistedFolders: [], // KubernetesApplicationPersistedFolderFormValue list
|
||||
PublishedPorts: [], // KubernetesApplicationPublishedPortFormValue list
|
||||
MemoryLimit: 0,
|
||||
CpuLimit: 0,
|
||||
DeploymentType: KubernetesApplicationDeploymentTypes.REPLICATED,
|
||||
PublishingType: KubernetesApplicationPublishingTypes.INTERNAL,
|
||||
DataAccessPolicy: KubernetesApplicationDataAccessPolicies.SHARED,
|
||||
Configurations: [], // KubernetesApplicationConfigurationFormValue list
|
||||
Containers: [],
|
||||
ReplicaCount: 1,
|
||||
AutoScaler: {},
|
||||
Containers: [],
|
||||
EnvironmentVariables: [], // KubernetesApplicationEnvironmentVariableFormValue list
|
||||
DataAccessPolicy: KubernetesApplicationDataAccessPolicies.SHARED,
|
||||
PersistedFolders: [], // KubernetesApplicationPersistedFolderFormValue list
|
||||
Configurations: [], // KubernetesApplicationConfigurationFormValue list
|
||||
PublishingType: KubernetesApplicationPublishingTypes.INTERNAL,
|
||||
PublishedPorts: [], // KubernetesApplicationPublishedPortFormValue list
|
||||
PlacementType: KubernetesApplicationPlacementTypes.PREFERRED,
|
||||
Placements: [], // KubernetesApplicationPlacementFormValue list
|
||||
OriginalIngresses: undefined,
|
||||
});
|
||||
|
||||
|
@ -123,6 +125,15 @@ export function KubernetesApplicationPublishedPortFormValue() {
|
|||
};
|
||||
}
|
||||
|
||||
export function KubernetesApplicationPlacementFormValue() {
|
||||
return {
|
||||
Label: {},
|
||||
Value: '',
|
||||
NeedsDeletion: false,
|
||||
IsNew: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesApplicationAutoScalerFormValue Model
|
||||
*/
|
||||
|
|
|
@ -27,6 +27,11 @@ export const KubernetesApplicationPublishingTypes = Object.freeze({
|
|||
INGRESS: 4,
|
||||
});
|
||||
|
||||
export const KubernetesApplicationPlacementTypes = Object.freeze({
|
||||
PREFERRED: 1,
|
||||
MANDATORY: 2,
|
||||
});
|
||||
|
||||
export const KubernetesApplicationQuotaDefaults = {
|
||||
CpuLimit: 0.1,
|
||||
MemoryLimit: 64, // MB
|
||||
|
|
|
@ -15,6 +15,7 @@ const _KubernetesDaemonSet = Object.freeze({
|
|||
ApplicationName: '',
|
||||
ApplicationOwner: '',
|
||||
Note: '',
|
||||
Affinity: undefined, // KubernetesPodAffinity
|
||||
});
|
||||
|
||||
export class KubernetesDaemonSet {
|
||||
|
|
|
@ -16,6 +16,7 @@ const _KubernetesDeployment = Object.freeze({
|
|||
ApplicationName: '',
|
||||
ApplicationOwner: '',
|
||||
Note: '',
|
||||
Affinity: undefined, // KubernetesPodAffinity
|
||||
});
|
||||
|
||||
export class KubernetesDeployment {
|
||||
|
|
|
@ -26,6 +26,7 @@ const _KubernetesDeploymentCreatePayload = Object.freeze({
|
|||
},
|
||||
},
|
||||
spec: {
|
||||
affinity: {},
|
||||
containers: [
|
||||
{
|
||||
name: '',
|
||||
|
|
|
@ -18,6 +18,7 @@ const _KubernetesStatefulSet = Object.freeze({
|
|||
ApplicationName: '',
|
||||
ApplicationOwner: '',
|
||||
Note: '',
|
||||
Affinity: undefined, // KubernetesPodAffinity
|
||||
});
|
||||
|
||||
export class KubernetesStatefulSet {
|
||||
|
|
|
@ -20,4 +20,15 @@ export class KubernetesNodeHelper {
|
|||
return label;
|
||||
});
|
||||
}
|
||||
|
||||
static generateNodeLabelsFromNodes(nodes) {
|
||||
const pairs = _.flatMap(nodes, (node) => {
|
||||
return _.map(_.toPairs(node.Labels), ([k, v]) => {
|
||||
return { key: k, value: v };
|
||||
});
|
||||
});
|
||||
return _.map(_.groupBy(pairs, 'key'), (values, key) => {
|
||||
return { Key: key, Values: _.uniq(_.map(values, 'value')) };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ function computeContainerStatus(statuses, name) {
|
|||
function computeAffinity(affinity) {
|
||||
const res = new KubernetesPodAffinity();
|
||||
if (affinity) {
|
||||
res.NodeAffinity = affinity.nodeAffinity || {};
|
||||
res.nodeAffinity = affinity.nodeAffinity || {};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -10,56 +10,13 @@ export const KubernetesPodNodeAffinityNodeSelectorRequirementOperators = Object.
|
|||
/**
|
||||
* KubernetesPodAffinity Model
|
||||
*/
|
||||
const _KubernetesPodAffinity = Object.freeze({
|
||||
NodeAffinity: {},
|
||||
// PodAffinity: {},
|
||||
// PodAntiAffinity: {},
|
||||
});
|
||||
|
||||
export class KubernetesPodAffinity {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodAffinity)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesPodNodeAffinity Model
|
||||
*/
|
||||
const _KubernetesPodNodeAffinity = Object.freeze({
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: [],
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: {},
|
||||
});
|
||||
|
||||
export class KubernetesPodNodeAffinity {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodNodeAffinity)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesPodPodAffinity Model
|
||||
*/
|
||||
const _KubernetesPodPodAffinity = Object.freeze({
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: [],
|
||||
equiredDuringSchedulingIgnoredDuringExecution: [],
|
||||
});
|
||||
|
||||
export class KubernetesPodPodAffinity {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodPodAffinity)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesPodPodAntiAffinity Model
|
||||
*/
|
||||
const _KubernetesPodPodAntiAffinity = Object.freeze({
|
||||
preferredDuringSchedulingIgnoredDuringExecution: [],
|
||||
requiredDuringSchedulingIgnoredDuringExecution: [],
|
||||
});
|
||||
|
||||
export class KubernetesPodPodAntiAffinity {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodPodAntiAffinity)));
|
||||
}
|
||||
// this model will contain non transformed data (raw payload data)
|
||||
// either during creation flow (model > api)
|
||||
// than during reading flow (api > model)
|
||||
export function KubernetesPodAffinity() {
|
||||
return {
|
||||
nodeAffinity: {}, // KubernetesPodNodeAffinityPayload
|
||||
// podAffinity: {},
|
||||
// podAntiAffinity: {},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ const _KubernetesPod = Object.freeze({
|
|||
Labels: [],
|
||||
Affinity: {}, // KubernetesPodAffinity
|
||||
Tolerations: [], // KubernetesPodToleration[]
|
||||
NodeSelector: undefined,
|
||||
});
|
||||
|
||||
export class KubernetesPod {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
export function KubernetesPodNodeAffinityPayload() {
|
||||
return {
|
||||
requiredDuringSchedulingIgnoredDuringExecution: {
|
||||
nodeSelectorTerms: [], // []KubernetesNodeSelectorTermPayload
|
||||
},
|
||||
preferredDuringSchedulingIgnoredDuringExecution: [], // []KubernetesPreferredSchedulingTermPayload
|
||||
};
|
||||
}
|
||||
|
||||
export function KubernetesPreferredSchedulingTermPayload() {
|
||||
return {
|
||||
weight: 1,
|
||||
preference: {}, // KubernetesNodeSelectorTermPayload
|
||||
};
|
||||
}
|
||||
|
||||
export function KubernetesNodeSelectorTermPayload() {
|
||||
return {
|
||||
matchExpressions: [], // []KubernetesNodeSelectorRequirementPayload
|
||||
};
|
||||
}
|
||||
|
||||
export function KubernetesNodeSelectorRequirementPayload() {
|
||||
return {
|
||||
key: '', // string
|
||||
operator: '', // KubernetesPodNodeAffinityNodeSelectorRequirementOperators
|
||||
values: [], // []string
|
||||
};
|
||||
}
|
|
@ -111,10 +111,9 @@ class KubernetesApplicationService {
|
|||
const boundService = KubernetesServiceHelper.findApplicationBoundService(services, rootItem.value.Raw);
|
||||
const service = boundService ? await this.KubernetesServiceService.get(namespace, boundService.metadata.name) : {};
|
||||
|
||||
const application = converterFunc(rootItem.value.Raw, service.Raw, ingresses.value);
|
||||
const application = converterFunc(rootItem.value.Raw, pods.value, service.Raw, ingresses.value);
|
||||
application.Yaml = rootItem.value.Yaml;
|
||||
application.Raw = rootItem.value.Raw;
|
||||
application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods.value, application.Raw);
|
||||
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
|
||||
|
||||
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
|
||||
|
@ -143,8 +142,7 @@ class KubernetesApplicationService {
|
|||
|
||||
const convertToApplication = (item, converterFunc, services, pods, ingresses) => {
|
||||
const service = KubernetesServiceHelper.findApplicationBoundService(services, item);
|
||||
const application = converterFunc(item, service, ingresses);
|
||||
application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, item);
|
||||
const application = converterFunc(item, pods, service, ingresses);
|
||||
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
|
||||
return application;
|
||||
};
|
||||
|
|
|
@ -926,6 +926,102 @@
|
|||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<div ng-if="ctrl.formValues.DeploymentType === ctrl.ApplicationDeploymentTypes.REPLICATED">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Placement preferences and constraints
|
||||
</div>
|
||||
<!-- #region PLACEMENTS -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Deploy this application on nodes that respect all of the following placement rules.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Placement rules</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addPlacement()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add rule
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="placement in ctrl.formValues.Placements" style="margin-top: 2px;">
|
||||
<div class="col-sm-5 input-group" ng-class="{ striked: placement.NeedsDeletion }">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="placement.Label"
|
||||
ng-options="label as (label.Key | kubernetesNodeLabelHumanReadbleText) for label in ctrl.nodesLabels"
|
||||
ng-change="ctrl.onPlacementLabelChange($index)"
|
||||
ng-disabled="ctrl.isEditAndNotNewPlacement($index)"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-5 input-group" ng-class="{ striked: placement.NeedsDeletion }">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="placement.Value"
|
||||
ng-options="value for value in placement.Label.Values"
|
||||
ng-disabled="ctrl.isEditAndNotNewPlacement($index)"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1 input-group">
|
||||
<button ng-if="!placement.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removePlacement($index)">
|
||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button ng-if="placement.NeedsDeletion" class="btn btn-sm btn-primary" type="button" ng-click="ctrl.restorePlacement($index)">
|
||||
<i class="fa fa-trash-restore" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="ctrl.showPlacementPolicySection()">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">Placement policy</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
Specify the policy associated to the placement rules.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- placement policy options -->
|
||||
<div class="form-group" style="margin-bottom: 0;" ng-if="ctrl.formValues.Placements.length">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="placement_soft" ng-value="ctrl.ApplicationPlacementTypes.PREFERRED" ng-model="ctrl.formValues.PlacementType" />
|
||||
<label for="placement_soft">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-list" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Preferred
|
||||
</div>
|
||||
<p>Schedule this application on nodes that match the rules if possible</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="placement_hard" ng-value="ctrl.ApplicationPlacementTypes.MANDATORY" ng-model="ctrl.formValues.PlacementType" />
|
||||
<label for="placement_hard">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-tasks" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Mandatory
|
||||
</div>
|
||||
<p>Schedule this application on nodes that match the rules only</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !placement policy options -->
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Publishing the application
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
KubernetesApplicationPublishingTypes,
|
||||
KubernetesApplicationQuotaDefaults,
|
||||
KubernetesApplicationTypes,
|
||||
KubernetesApplicationPlacementTypes,
|
||||
} from 'Kubernetes/models/application/models';
|
||||
import {
|
||||
KubernetesApplicationConfigurationFormValue,
|
||||
|
@ -18,6 +19,7 @@ import {
|
|||
KubernetesApplicationFormValues,
|
||||
KubernetesApplicationPersistedFolderFormValue,
|
||||
KubernetesApplicationPublishedPortFormValue,
|
||||
KubernetesApplicationPlacementFormValue,
|
||||
} from 'Kubernetes/models/application/formValues';
|
||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
||||
|
@ -25,6 +27,7 @@ import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceRese
|
|||
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application/index';
|
||||
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||
import { KubernetesNodeHelper } from 'Kubernetes/node/helper';
|
||||
|
||||
class KubernetesCreateApplicationController {
|
||||
/* #region CONSTRUCTOR */
|
||||
|
@ -66,6 +69,7 @@ class KubernetesCreateApplicationController {
|
|||
this.ApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
||||
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
|
||||
this.ApplicationPublishingTypes = KubernetesApplicationPublishingTypes;
|
||||
this.ApplicationPlacementTypes = KubernetesApplicationPlacementTypes;
|
||||
this.ApplicationTypes = KubernetesApplicationTypes;
|
||||
this.ApplicationConfigurationFormValueOverridenKeyTypes = KubernetesApplicationConfigurationFormValueOverridenKeyTypes;
|
||||
this.ServiceTypes = KubernetesServiceTypes;
|
||||
|
@ -248,6 +252,33 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region PLACEMENT UI MANAGEMENT */
|
||||
addPlacement() {
|
||||
const placement = new KubernetesApplicationPlacementFormValue();
|
||||
const label = this.nodesLabels[0];
|
||||
placement.Label = label;
|
||||
placement.Value = label.Values[0];
|
||||
this.formValues.Placements.push(placement);
|
||||
}
|
||||
|
||||
restorePlacement(index) {
|
||||
this.formValues.Placements[index].NeedsDeletion = false;
|
||||
}
|
||||
|
||||
removePlacement(index) {
|
||||
if (this.state.isEdit && !this.formValues.Placements[index].IsNew) {
|
||||
this.formValues.Placements[index].NeedsDeletion = true;
|
||||
} else {
|
||||
this.formValues.Placements.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
onPlacementLabelChange(index) {
|
||||
this.formValues.Placements[index].Value = this.formValues.Placements[index].Label.Values[0];
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
||||
/* #region PUBLISHED PORTS UI MANAGEMENT */
|
||||
addPublishedPort() {
|
||||
const p = new KubernetesApplicationPublishedPortFormValue();
|
||||
|
@ -512,6 +543,15 @@ class KubernetesCreateApplicationController {
|
|||
return this.formValues.PublishingType !== KubernetesApplicationPublishingTypes.INTERNAL && toKeepPorts.length === 0;
|
||||
}
|
||||
|
||||
isEditAndNotNewPlacement(index) {
|
||||
return this.state.isEdit && !this.formValues.Placements[index].IsNew;
|
||||
}
|
||||
|
||||
showPlacementPolicySection() {
|
||||
const placements = _.filter(this.formValues.Placements, { NeedsDeletion: false });
|
||||
return placements.length !== 0;
|
||||
}
|
||||
|
||||
isNonScalable() {
|
||||
const scalable = this.supportScalableReplicaDeployment();
|
||||
const global = this.supportGlobalDeployment();
|
||||
|
@ -861,13 +901,20 @@ class KubernetesCreateApplicationController {
|
|||
this.state.nodes.memory += filesizeParser(item.Memory);
|
||||
this.state.nodes.cpu += item.CPU;
|
||||
});
|
||||
this.nodesLabels = KubernetesNodeHelper.generateNodeLabelsFromNodes(nodes);
|
||||
|
||||
const namespace = this.state.isEdit ? this.state.params.namespace : this.formValues.ResourcePool.Namespace.Name;
|
||||
await this.refreshNamespaceData(namespace);
|
||||
|
||||
if (this.state.isEdit) {
|
||||
await this.getApplication();
|
||||
this.formValues = KubernetesApplicationConverter.applicationToFormValues(this.application, this.resourcePools, this.configurations, this.persistentVolumeClaims);
|
||||
this.formValues = KubernetesApplicationConverter.applicationToFormValues(
|
||||
this.application,
|
||||
this.resourcePools,
|
||||
this.configurations,
|
||||
this.persistentVolumeClaims,
|
||||
this.nodesLabels
|
||||
);
|
||||
this.formValues.OriginalIngresses = this.filteredIngresses;
|
||||
this.savedFormValues = angular.copy(this.formValues);
|
||||
delete this.formValues.ApplicationType;
|
||||
|
|
|
@ -53,8 +53,8 @@ function computeAffinities(nodes, application) {
|
|||
}
|
||||
}
|
||||
|
||||
if (pod.Affinity.NodeAffinity.requiredDuringSchedulingIgnoredDuringExecution) {
|
||||
const unmatchedTerms = _.map(pod.Affinity.NodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms, (t) => {
|
||||
if (pod.Affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution) {
|
||||
const unmatchedTerms = _.map(pod.Affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms, (t) => {
|
||||
const unmatchedExpressions = _.map(t.matchExpressions, (e) => {
|
||||
const exists = {}.hasOwnProperty.call(n.Labels, e.key);
|
||||
const isIn = exists && _.includes(e.values, n.Labels[e.key]);
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
<!-- ADMIN + UNMET TAINTS -->
|
||||
<tr ng-if="$ctrl.isAdmin" ng-show="item.Expanded" ng-repeat="taint in item.UnmetTaints" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }">
|
||||
<td colspan="2">
|
||||
This application is missing a toleration for the taint <code>{{ taint.Key }}{{ taint.Value ? '=' + taint.Value : '' }}:{{ taint.effect }}</code>
|
||||
This application is missing a toleration for the taint <code>{{ taint.Key }}{{ taint.Value ? '=' + taint.Value : '' }}:{{ taint.Effect }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !ADMIN + UNMET TAINTS -->
|
||||
|
|
Loading…
Reference in New Issue