mirror of https://github.com/portainer/portainer
feat(resourcequotas): reduce resource quota requests [EE-4757] (#8420)
parent
44d69f3a3f
commit
9f6702d0b8
|
@ -1,5 +1,3 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import angular from 'angular';
|
||||
import PortainerError from 'Portainer/error';
|
||||
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
||||
|
@ -65,10 +63,13 @@ class KubernetesNamespaceService {
|
|||
|
||||
async getAllAsync() {
|
||||
try {
|
||||
// get the list of all namespaces (RBAC allows users to see the list of namespaces)
|
||||
const data = await this.KubernetesNamespaces().get().$promise;
|
||||
const promises = _.map(data.items, (item) => this.KubernetesNamespaces().status({ id: item.metadata.name }).$promise);
|
||||
// get the status of each namespace (RBAC will give permission denied for status of unauthorised namespaces)
|
||||
const promises = data.items.map((item) => this.KubernetesNamespaces().status({ id: item.metadata.name }).$promise);
|
||||
const namespaces = await $allSettled(promises);
|
||||
const allNamespaces = _.map(namespaces.fulfilled, (item) => {
|
||||
// only return namespaces if the user has access to namespaces
|
||||
const allNamespaces = namespaces.fulfilled.map((item) => {
|
||||
return KubernetesNamespaceConverter.apiToNamespace(item);
|
||||
});
|
||||
updateNamespaces(allNamespaces);
|
||||
|
|
|
@ -21,27 +21,33 @@ export function KubernetesResourcePoolService(
|
|||
toggleSystem,
|
||||
};
|
||||
|
||||
async function getOne(name) {
|
||||
// getting quota isn't a costly operation for one namespace, so we can get it by default
|
||||
async function getOne(name, { getQuota = true }) {
|
||||
const namespace = await KubernetesNamespaceService.get(name);
|
||||
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
||||
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
||||
if (quotaAttempt.status === 'fulfilled') {
|
||||
pool.Quota = quotaAttempt.value;
|
||||
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
|
||||
if (getQuota) {
|
||||
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
||||
if (quotaAttempt.status === 'fulfilled') {
|
||||
pool.Quota = quotaAttempt.value;
|
||||
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
|
||||
}
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
async function getAll() {
|
||||
// getting the quota for all namespaces is costly by default, so disable getting it by default
|
||||
async function getAll({ getQuota = false }) {
|
||||
const namespaces = await KubernetesNamespaceService.get();
|
||||
const pools = await Promise.all(
|
||||
_.map(namespaces, async (namespace) => {
|
||||
const name = namespace.Name;
|
||||
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
||||
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
||||
if (quotaAttempt.status === 'fulfilled') {
|
||||
pool.Quota = quotaAttempt.value;
|
||||
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
|
||||
if (getQuota) {
|
||||
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
||||
if (quotaAttempt.status === 'fulfilled') {
|
||||
pool.Quota = quotaAttempt.value;
|
||||
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
|
||||
}
|
||||
}
|
||||
return pool;
|
||||
})
|
||||
|
@ -49,11 +55,11 @@ export function KubernetesResourcePoolService(
|
|||
return pools;
|
||||
}
|
||||
|
||||
function get(name) {
|
||||
function get(name, options = {}) {
|
||||
if (name) {
|
||||
return $async(getOne, name);
|
||||
return $async(getOne, name, options);
|
||||
}
|
||||
return $async(getAll);
|
||||
return $async(getAll, options);
|
||||
}
|
||||
|
||||
function create(formValues) {
|
||||
|
|
|
@ -872,8 +872,8 @@ class KubernetesCreateApplicationController {
|
|||
/* #endregion */
|
||||
|
||||
/* #region DATA AUTO REFRESH */
|
||||
updateSliders() {
|
||||
const quota = this.formValues.ResourcePool.Quota;
|
||||
updateSliders(namespaceWithQuota) {
|
||||
const quota = namespaceWithQuota.Quota;
|
||||
let minCpu = 0,
|
||||
minMemory = 0,
|
||||
maxCpu = this.state.namespaceLimits.cpu,
|
||||
|
@ -906,33 +906,36 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
}
|
||||
|
||||
updateNamespaceLimits() {
|
||||
let maxCpu = this.state.nodes.cpu;
|
||||
let maxMemory = this.state.nodes.memory;
|
||||
const quota = this.formValues.ResourcePool.Quota;
|
||||
updateNamespaceLimits(namespaceWithQuota) {
|
||||
return this.$async(async () => {
|
||||
let maxCpu = this.state.nodes.cpu;
|
||||
let maxMemory = this.state.nodes.memory;
|
||||
|
||||
this.state.resourcePoolHasQuota = false;
|
||||
const quota = namespaceWithQuota.Quota;
|
||||
|
||||
if (quota) {
|
||||
if (quota.CpuLimit) {
|
||||
this.state.resourcePoolHasQuota = true;
|
||||
maxCpu = quota.CpuLimit - quota.CpuLimitUsed;
|
||||
if (this.state.isEdit && this.savedFormValues.CpuLimit) {
|
||||
maxCpu += this.savedFormValues.CpuLimit * this.effectiveInstances();
|
||||
this.state.resourcePoolHasQuota = false;
|
||||
|
||||
if (quota) {
|
||||
if (quota.CpuLimit) {
|
||||
this.state.resourcePoolHasQuota = true;
|
||||
maxCpu = quota.CpuLimit - quota.CpuLimitUsed;
|
||||
if (this.state.isEdit && this.savedFormValues.CpuLimit) {
|
||||
maxCpu += this.savedFormValues.CpuLimit * this.effectiveInstances();
|
||||
}
|
||||
}
|
||||
|
||||
if (quota.MemoryLimit) {
|
||||
this.state.resourcePoolHasQuota = true;
|
||||
maxMemory = quota.MemoryLimit - quota.MemoryLimitUsed;
|
||||
if (this.state.isEdit && this.savedFormValues.MemoryLimit) {
|
||||
maxMemory += KubernetesResourceReservationHelper.bytesValue(this.savedFormValues.MemoryLimit) * this.effectiveInstances();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (quota.MemoryLimit) {
|
||||
this.state.resourcePoolHasQuota = true;
|
||||
maxMemory = quota.MemoryLimit - quota.MemoryLimitUsed;
|
||||
if (this.state.isEdit && this.savedFormValues.MemoryLimit) {
|
||||
maxMemory += KubernetesResourceReservationHelper.bytesValue(this.savedFormValues.MemoryLimit) * this.effectiveInstances();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.state.namespaceLimits.cpu = maxCpu;
|
||||
this.state.namespaceLimits.memory = maxMemory;
|
||||
this.state.namespaceLimits.cpu = maxCpu;
|
||||
this.state.namespaceLimits.memory = maxMemory;
|
||||
});
|
||||
}
|
||||
|
||||
refreshStacks(namespace) {
|
||||
|
@ -1026,9 +1029,10 @@ class KubernetesCreateApplicationController {
|
|||
|
||||
onResourcePoolSelectionChange() {
|
||||
return this.$async(async () => {
|
||||
const namespaceWithQuota = await this.KubernetesResourcePoolService.get(this.formValues.ResourcePool.Namespace.Name);
|
||||
const namespace = this.formValues.ResourcePool.Namespace.Name;
|
||||
this.updateNamespaceLimits();
|
||||
this.updateSliders();
|
||||
this.updateNamespaceLimits(namespaceWithQuota);
|
||||
this.updateSliders(namespaceWithQuota);
|
||||
await this.refreshNamespaceData(namespace);
|
||||
this.resetFormValues();
|
||||
});
|
||||
|
@ -1222,7 +1226,9 @@ class KubernetesCreateApplicationController {
|
|||
this.allNamespaces = resourcePools.map(({ Namespace }) => Namespace.Name);
|
||||
this.resourcePools = _.sortBy(nonSystemNamespaces, ({ Namespace }) => (Namespace.Name === 'default' ? 0 : 1));
|
||||
|
||||
const namespaceWithQuota = await this.KubernetesResourcePoolService.get(this.resourcePools[0].Namespace.Name);
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
this.formValues.ResourcePool.Quota = namespaceWithQuota.Quota;
|
||||
if (!this.formValues.ResourcePool) {
|
||||
return;
|
||||
}
|
||||
|
@ -1289,8 +1295,8 @@ class KubernetesCreateApplicationController {
|
|||
|
||||
this.oldFormValues = angular.copy(this.formValues);
|
||||
|
||||
this.updateNamespaceLimits();
|
||||
this.updateSliders();
|
||||
this.updateNamespaceLimits(namespaceWithQuota);
|
||||
this.updateSliders(namespaceWithQuota);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||
} finally {
|
||||
|
|
|
@ -151,7 +151,7 @@ class KubernetesCreateResourcePoolController {
|
|||
getResourcePools() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve namespaces');
|
||||
}
|
||||
|
|
|
@ -378,7 +378,7 @@ class KubernetesResourcePoolController {
|
|||
|
||||
const name = this.$state.params.id;
|
||||
|
||||
const [nodes, pools] = await Promise.all([this.KubernetesNodeService.get(), this.KubernetesResourcePoolService.get()]);
|
||||
const [nodes, pools] = await Promise.all([this.KubernetesNodeService.get(), this.KubernetesResourcePoolService.get('', { getQuota: true })]);
|
||||
|
||||
this.ingressControllers = [];
|
||||
if (this.state.ingressAvailabilityPerNamespace) {
|
||||
|
|
|
@ -62,7 +62,7 @@ class KubernetesResourcePoolsController {
|
|||
|
||||
async getResourcePoolsAsync() {
|
||||
try {
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retreive namespaces');
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue