mirror of
https://github.com/portainer/portainer.git
synced 2025-07-29 18:29:44 +02:00
Merge branch 'develop' into feat/EE-809/EE-466/kube-advanced-apps
This commit is contained in:
commit
1cbfd96738
45 changed files with 901 additions and 166 deletions
|
@ -51,7 +51,8 @@ class KubernetesCreateApplicationController {
|
|||
KubernetesPersistentVolumeClaimService,
|
||||
KubernetesVolumeService,
|
||||
RegistryService,
|
||||
StackService
|
||||
StackService,
|
||||
KubernetesNodesLimitsService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
|
@ -68,6 +69,7 @@ class KubernetesCreateApplicationController {
|
|||
this.KubernetesPersistentVolumeClaimService = KubernetesPersistentVolumeClaimService;
|
||||
this.RegistryService = RegistryService;
|
||||
this.StackService = StackService;
|
||||
this.KubernetesNodesLimitsService = KubernetesNodesLimitsService;
|
||||
|
||||
this.ApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
||||
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
|
||||
|
@ -98,6 +100,10 @@ class KubernetesCreateApplicationController {
|
|||
memory: 0,
|
||||
cpu: 0,
|
||||
},
|
||||
namespaceLimits: {
|
||||
memory: 0,
|
||||
cpu: 0,
|
||||
},
|
||||
resourcePoolHasQuota: false,
|
||||
viewReady: false,
|
||||
availableSizeUnits: ['MB', 'GB', 'TB'],
|
||||
|
@ -406,7 +412,7 @@ class KubernetesCreateApplicationController {
|
|||
/* #region PUBLISHED PORTS UI MANAGEMENT */
|
||||
addPublishedPort() {
|
||||
const p = new KubernetesApplicationPublishedPortFormValue();
|
||||
const ingresses = this.filteredIngresses;
|
||||
const ingresses = this.ingresses;
|
||||
p.IngressName = ingresses && ingresses.length ? ingresses[0].Name : undefined;
|
||||
p.IngressHost = ingresses && ingresses.length ? ingresses[0].Hosts[0] : undefined;
|
||||
p.IngressHosts = ingresses && ingresses.length ? ingresses[0].Hosts : undefined;
|
||||
|
@ -417,7 +423,7 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
|
||||
resetPublishedPorts() {
|
||||
const ingresses = this.filteredIngresses;
|
||||
const ingresses = this.ingresses;
|
||||
_.forEach(this.formValues.PublishedPorts, (p) => {
|
||||
p.IngressName = ingresses && ingresses.length ? ingresses[0].Name : undefined;
|
||||
p.IngressHost = ingresses && ingresses.length ? ingresses[0].Hosts[0] : undefined;
|
||||
|
@ -476,7 +482,7 @@ class KubernetesCreateApplicationController {
|
|||
|
||||
onChangePortMappingIngress(index) {
|
||||
const publishedPort = this.formValues.PublishedPorts[index];
|
||||
const ingress = _.find(this.filteredIngresses, { Name: publishedPort.IngressName });
|
||||
const ingress = _.find(this.ingresses, { Name: publishedPort.IngressName });
|
||||
publishedPort.IngressHosts = ingress.Hosts;
|
||||
this.ingressHostnames = ingress.Hosts;
|
||||
publishedPort.IngressHost = this.ingressHostnames.length ? this.ingressHostnames[0] : [];
|
||||
|
@ -624,14 +630,28 @@ class KubernetesCreateApplicationController {
|
|||
return !this.state.sliders.memory.max || !this.state.sliders.cpu.max;
|
||||
}
|
||||
|
||||
resourceReservationsOverflow() {
|
||||
const instances = this.formValues.ReplicaCount;
|
||||
nodeLimitsOverflow() {
|
||||
const cpu = this.formValues.CpuLimit;
|
||||
const maxCpu = this.state.sliders.cpu.max;
|
||||
const memory = this.formValues.MemoryLimit;
|
||||
const maxMemory = this.state.sliders.memory.max;
|
||||
const memory = KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit);
|
||||
|
||||
if (cpu * instances > maxCpu) {
|
||||
const overflow = this.nodesLimits.overflowForReplica(cpu, memory, 1);
|
||||
|
||||
return overflow;
|
||||
}
|
||||
|
||||
effectiveInstances() {
|
||||
return this.formValues.DeploymentType === this.ApplicationDeploymentTypes.GLOBAL ? this.nodeNumber : this.formValues.ReplicaCount;
|
||||
}
|
||||
|
||||
resourceReservationsOverflow() {
|
||||
const instances = this.effectiveInstances();
|
||||
const cpu = this.formValues.CpuLimit;
|
||||
const maxCpu = this.state.namespaceLimits.cpu;
|
||||
const memory = KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit);
|
||||
const maxMemory = this.state.namespaceLimits.memory;
|
||||
|
||||
// multiply 1000 can avoid 0.1 * 3 > 0.3
|
||||
if (cpu * 1000 * instances > maxCpu * 1000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -639,17 +659,23 @@ class KubernetesCreateApplicationController {
|
|||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.REPLICATED) {
|
||||
return this.nodesLimits.overflowForReplica(cpu, memory, instances);
|
||||
}
|
||||
|
||||
// DeploymentType == GLOBAL
|
||||
return this.nodesLimits.overflowForGlobal(cpu, memory);
|
||||
}
|
||||
|
||||
autoScalerOverflow() {
|
||||
const instances = this.formValues.AutoScaler.MaxReplicas;
|
||||
const cpu = this.formValues.CpuLimit;
|
||||
const maxCpu = this.state.sliders.cpu.max;
|
||||
const memory = this.formValues.MemoryLimit;
|
||||
const maxMemory = this.state.sliders.memory.max;
|
||||
const maxCpu = this.state.namespaceLimits.cpu;
|
||||
const memory = KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit);
|
||||
const maxMemory = this.state.namespaceLimits.memory;
|
||||
|
||||
if (cpu * instances > maxCpu) {
|
||||
// multiply 1000 can avoid 0.1 * 3 > 0.3
|
||||
if (cpu * 1000 * instances > maxCpu * 1000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -657,7 +683,7 @@ class KubernetesCreateApplicationController {
|
|||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return this.nodesLimits.overflowForReplica(cpu, memory, instances);
|
||||
}
|
||||
|
||||
publishViaLoadBalancerEnabled() {
|
||||
|
@ -665,7 +691,7 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
|
||||
publishViaIngressEnabled() {
|
||||
return this.filteredIngresses.length;
|
||||
return this.ingresses.length;
|
||||
}
|
||||
|
||||
isEditAndNoChangesMade() {
|
||||
|
@ -773,50 +799,66 @@ class KubernetesCreateApplicationController {
|
|||
|
||||
/* #region DATA AUTO REFRESH */
|
||||
updateSliders() {
|
||||
const quota = this.formValues.ResourcePool.Quota;
|
||||
let minCpu = 0,
|
||||
minMemory = 0,
|
||||
maxCpu = this.state.namespaceLimits.cpu,
|
||||
maxMemory = this.state.namespaceLimits.memory;
|
||||
|
||||
if (quota) {
|
||||
if (quota.CpuLimit) {
|
||||
minCpu = KubernetesApplicationQuotaDefaults.CpuLimit;
|
||||
}
|
||||
if (quota.MemoryLimit) {
|
||||
minMemory = KubernetesResourceReservationHelper.bytesValue(KubernetesApplicationQuotaDefaults.MemoryLimit);
|
||||
}
|
||||
}
|
||||
|
||||
maxCpu = Math.min(maxCpu, this.nodesLimits.MaxCPU);
|
||||
maxMemory = Math.min(maxMemory, this.nodesLimits.MaxMemory);
|
||||
|
||||
if (maxMemory < minMemory) {
|
||||
minMemory = 0;
|
||||
maxMemory = 0;
|
||||
}
|
||||
|
||||
this.state.sliders.memory.min = KubernetesResourceReservationHelper.megaBytesValue(minMemory);
|
||||
this.state.sliders.memory.max = KubernetesResourceReservationHelper.megaBytesValue(maxMemory);
|
||||
this.state.sliders.cpu.min = minCpu;
|
||||
this.state.sliders.cpu.max = _.floor(maxCpu, 2);
|
||||
if (!this.state.isEdit) {
|
||||
this.formValues.CpuLimit = minCpu;
|
||||
this.formValues.MemoryLimit = KubernetesResourceReservationHelper.megaBytesValue(minMemory);
|
||||
}
|
||||
}
|
||||
|
||||
updateNamespaceLimits() {
|
||||
let maxCpu = this.state.nodes.cpu;
|
||||
let maxMemory = this.state.nodes.memory;
|
||||
const quota = this.formValues.ResourcePool.Quota;
|
||||
|
||||
this.state.resourcePoolHasQuota = false;
|
||||
|
||||
const quota = this.formValues.ResourcePool.Quota;
|
||||
let minCpu,
|
||||
maxCpu,
|
||||
minMemory,
|
||||
maxMemory = 0;
|
||||
if (quota) {
|
||||
if (quota.CpuLimit) {
|
||||
this.state.resourcePoolHasQuota = true;
|
||||
minCpu = KubernetesApplicationQuotaDefaults.CpuLimit;
|
||||
maxCpu = quota.CpuLimit - quota.CpuLimitUsed;
|
||||
if (this.state.isEdit && this.savedFormValues.CpuLimit) {
|
||||
maxCpu += this.savedFormValues.CpuLimit * this.savedFormValues.ReplicaCount;
|
||||
maxCpu += this.savedFormValues.CpuLimit * this.effectiveInstances();
|
||||
}
|
||||
} else {
|
||||
minCpu = 0;
|
||||
maxCpu = this.state.nodes.cpu;
|
||||
}
|
||||
|
||||
if (quota.MemoryLimit) {
|
||||
this.state.resourcePoolHasQuota = true;
|
||||
minMemory = KubernetesApplicationQuotaDefaults.MemoryLimit;
|
||||
maxMemory = quota.MemoryLimit - quota.MemoryLimitUsed;
|
||||
if (this.state.isEdit && this.savedFormValues.MemoryLimit) {
|
||||
maxMemory += KubernetesResourceReservationHelper.bytesValue(this.savedFormValues.MemoryLimit) * this.savedFormValues.ReplicaCount;
|
||||
maxMemory += KubernetesResourceReservationHelper.bytesValue(this.savedFormValues.MemoryLimit) * this.effectiveInstances();
|
||||
}
|
||||
} else {
|
||||
minMemory = 0;
|
||||
maxMemory = this.state.nodes.memory;
|
||||
}
|
||||
} else {
|
||||
minCpu = 0;
|
||||
maxCpu = this.state.nodes.cpu;
|
||||
minMemory = 0;
|
||||
maxMemory = this.state.nodes.memory;
|
||||
}
|
||||
this.state.sliders.memory.min = minMemory;
|
||||
this.state.sliders.memory.max = KubernetesResourceReservationHelper.megaBytesValue(maxMemory);
|
||||
this.state.sliders.cpu.min = minCpu;
|
||||
this.state.sliders.cpu.max = _.round(maxCpu, 2);
|
||||
if (!this.state.isEdit) {
|
||||
this.formValues.CpuLimit = minCpu;
|
||||
this.formValues.MemoryLimit = minMemory;
|
||||
}
|
||||
|
||||
this.state.namespaceLimits.cpu = maxCpu;
|
||||
this.state.namespaceLimits.memory = maxMemory;
|
||||
}
|
||||
|
||||
refreshStacks(namespace) {
|
||||
|
@ -870,16 +912,22 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
|
||||
refreshIngresses(namespace) {
|
||||
this.filteredIngresses = _.filter(this.ingresses, { Namespace: namespace });
|
||||
this.ingressHostnames = this.filteredIngresses.length ? this.filteredIngresses[0].Hosts : [];
|
||||
if (!this.publishViaIngressEnabled()) {
|
||||
if (this.savedFormValues) {
|
||||
this.formValues.PublishingType = this.savedFormValues.PublishingType;
|
||||
} else {
|
||||
this.formValues.PublishingType = this.ApplicationPublishingTypes.INTERNAL;
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.ingresses = await this.KubernetesIngressService.get(namespace);
|
||||
this.ingressHostnames = this.ingresses.length ? this.ingresses[0].Hosts : [];
|
||||
if (!this.publishViaIngressEnabled()) {
|
||||
if (this.savedFormValues) {
|
||||
this.formValues.PublishingType = this.savedFormValues.PublishingType;
|
||||
} else {
|
||||
this.formValues.PublishingType = this.ApplicationPublishingTypes.INTERNAL;
|
||||
}
|
||||
}
|
||||
this.formValues.OriginalIngresses = this.ingresses;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve ingresses');
|
||||
}
|
||||
}
|
||||
this.formValues.OriginalIngresses = this.filteredIngresses;
|
||||
});
|
||||
}
|
||||
|
||||
refreshNamespaceData(namespace) {
|
||||
|
@ -904,6 +952,7 @@ class KubernetesCreateApplicationController {
|
|||
onResourcePoolSelectionChange() {
|
||||
return this.$async(async () => {
|
||||
const namespace = this.formValues.ResourcePool.Namespace.Name;
|
||||
this.updateNamespaceLimits();
|
||||
this.updateSliders();
|
||||
await this.refreshNamespaceData(namespace);
|
||||
this.resetFormValues();
|
||||
|
@ -988,12 +1037,12 @@ class KubernetesCreateApplicationController {
|
|||
this.state.useLoadBalancer = this.endpoint.Kubernetes.Configuration.UseLoadBalancer;
|
||||
this.state.useServerMetrics = this.endpoint.Kubernetes.Configuration.UseServerMetrics;
|
||||
|
||||
const [resourcePools, nodes, ingresses] = await Promise.all([
|
||||
const [resourcePools, nodes, nodesLimits] = await Promise.all([
|
||||
this.KubernetesResourcePoolService.get(),
|
||||
this.KubernetesNodeService.get(),
|
||||
this.KubernetesIngressService.get(),
|
||||
this.KubernetesNodesLimitsService.get(),
|
||||
]);
|
||||
this.ingresses = ingresses;
|
||||
this.nodesLimits = nodesLimits;
|
||||
|
||||
this.resourcePools = _.filter(resourcePools, (resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
|
@ -1006,6 +1055,7 @@ class KubernetesCreateApplicationController {
|
|||
this.state.nodes.cpu += item.CPU;
|
||||
});
|
||||
this.nodesLabels = KubernetesNodeHelper.generateNodeLabelsFromNodes(nodes);
|
||||
this.nodeNumber = nodes.length;
|
||||
|
||||
const namespace = this.state.isEdit ? this.$state.params.namespace : this.formValues.ResourcePool.Namespace.Name;
|
||||
await this.refreshNamespaceData(namespace);
|
||||
|
@ -1018,7 +1068,7 @@ class KubernetesCreateApplicationController {
|
|||
this.configurations,
|
||||
this.persistentVolumeClaims,
|
||||
this.nodesLabels,
|
||||
this.filteredIngresses
|
||||
this.ingresses
|
||||
);
|
||||
|
||||
if (this.application.ApplicationKind) {
|
||||
|
@ -1031,7 +1081,7 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
}
|
||||
|
||||
this.formValues.OriginalIngresses = this.filteredIngresses;
|
||||
this.formValues.OriginalIngresses = this.ingresses;
|
||||
this.formValues.ImageModel = await this.parseImageConfiguration(this.formValues.ImageModel);
|
||||
this.savedFormValues = angular.copy(this.formValues);
|
||||
delete this.formValues.ApplicationType;
|
||||
|
@ -1048,8 +1098,13 @@ class KubernetesCreateApplicationController {
|
|||
await this.refreshNamespaceData(namespace);
|
||||
} else {
|
||||
this.formValues.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(null, this.formValues.ReplicaCount);
|
||||
this.formValues.OriginalIngressClasses = angular.copy(this.ingresses);
|
||||
}
|
||||
|
||||
if (this.state.isEdit) {
|
||||
this.nodesLimits.excludesPods(this.application.Pods, this.formValues.CpuLimit, KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit));
|
||||
}
|
||||
|
||||
this.updateNamespaceLimits();
|
||||
this.updateSliders();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue