portainer/app/react/kubernetes/applications/components/ResourceReservationFormSection/resourceReservationValidati...

118 lines
3.9 KiB
TypeScript
Raw Normal View History

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<string, NodeLimit>;
type ValidationData = {
maxMemoryLimit: number;
maxCpuLimit: number;
isEnvironmentAdmin: boolean;
nodeLimits: NodesLimits;
};
export function resourceReservationValidation(
validationData?: ValidationData
): SchemaOf<ResourceQuotaFormValues> {
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
) {
const nanParsedMemoryLimit = Number.isNaN(memoryLimit) ? 0 : memoryLimit;
const nanParsedCPULimit = Number.isNaN(cpuLimit) ? 0 : cpuLimit;
// 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 >= nanParsedMemoryLimit &&
nodeLimit.CPU >= nanParsedCPULimit
);
}