import { SchemaOf, TestContext, number, object } from 'yup'; import KubernetesResourceReservationHelper from '@/kubernetes/helpers/resourceReservationHelper'; import { nanNumberSchema } from '@/react-tools/yup-schemas'; import { ResourceQuotaFormValues } from './types'; type NodeLimit = { CPU: number; Memory: number; }; type NodesLimits = Record; type ValidationData = { maxMemoryLimit: number; maxCpuLimit: number; isEnvironmentAdmin: boolean; nodeLimits: NodesLimits; }; export function resourceReservationValidation( validationData?: ValidationData ): SchemaOf { return object().shape({ memoryLimit: nanNumberSchema('Memory limit is required.') .min(0, 'Value must be greater than or equal to 0.') .test( 'exhaused', `The memory capacity for this namespace has been exhausted, so you cannot deploy the application.${ validationData?.isEnvironmentAdmin ? '' : ' Contact your administrator to expand the memory capacity of the namespace.' }`, () => !!validationData && validationData.maxMemoryLimit > 0 ) .max( validationData?.maxMemoryLimit || 0, ({ value }) => `Value must be between 0 and ${validationData?.maxMemoryLimit}MB now - the previous value of ${value} exceeds this.` ) .test( 'hasSuitableNode', `These reservations would exceed the resources currently available in the cluster.`, // eslint-disable-next-line prefer-arrow-callback, func-names function (value: number | undefined, context: TestContext) { if (!validationData || value === undefined) { // explicitely check for undefined, since 0 is a valid value return true; } const { memoryLimit, cpuLimit } = context.parent; return hasSuitableNode( memoryLimit, cpuLimit, validationData.nodeLimits ); } ) .required('Memory limit is required.'), cpuLimit: number() .min(0) .test( 'exhaused', `The CPU capacity for this namespace has been exhausted, so you cannot deploy the application.${ validationData?.isEnvironmentAdmin ? '' : ' Contact your administrator to expand the CPU capacity of the namespace.' }`, () => !!validationData && validationData.maxCpuLimit > 0 ) .max( validationData?.maxCpuLimit || 0, ({ value }) => `Value must be between 0 and ${validationData?.maxCpuLimit} now - the previous value of ${value} exceeds this.` ) .test( 'hasSuitableNode', `These reservations would exceed the resources currently available in the cluster.`, // eslint-disable-next-line prefer-arrow-callback, func-names function (value: number | undefined, context: TestContext) { if (!validationData || value === undefined) { // explicitely check for undefined, since 0 is a valid value return true; } const { memoryLimit, cpuLimit } = context.parent; return hasSuitableNode( memoryLimit, cpuLimit, validationData.nodeLimits ); } ) .required(), }); } function hasSuitableNode( memoryLimit: number, cpuLimit: number, nodeLimits: NodesLimits ) { // transform the nodelimits from bytes to MB const limits = Object.values(nodeLimits).map((nodeLimit) => ({ ...nodeLimit, Memory: KubernetesResourceReservationHelper.megaBytesValue( nodeLimit.Memory ), })); // make sure there's a node available with enough memory and cpu return limits.some( (nodeLimit) => nodeLimit.Memory >= memoryLimit && nodeLimit.CPU >= cpuLimit ); }