feat(resourcequotas): reduce resource quota requests [EE-4757] (#8420)

pull/8445/head
Ali 2023-02-10 18:28:53 +13:00 committed by GitHub
parent 44d69f3a3f
commit 9f6702d0b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 48 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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 {

View File

@ -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');
}

View File

@ -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) {

View File

@ -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');
}