refactor(app): migrate resource reservations [EE-6236] (#10695)

* refactor(app): migrate resource reservations [EE-6236]
pull/10705/head
Ali 11 months ago committed by GitHub
parent e07ee05ee7
commit 947ba4940b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -30,6 +30,10 @@ import { configurationsValidationSchema } from '@/react/kubernetes/applications/
import { ConfigMapsFormSection } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigMapsFormSection';
import { PersistedFoldersFormSection } from '@/react/kubernetes/applications/components/PersistedFoldersFormSection';
import { persistedFoldersValidation } from '@/react/kubernetes/applications/components/PersistedFoldersFormSection/persistedFoldersValidation';
import {
ResourceReservationFormSection,
resourceReservationValidation,
} from '@/react/kubernetes/applications/components/ResourceReservationFormSection';
import { EnvironmentVariablesFieldset } from '@@/form-components/EnvironmentVariablesFieldset';
@ -221,3 +225,16 @@ withFormValidation(
],
persistedFoldersValidation
);
withFormValidation(
ngModule,
withUIRouter(withCurrentUser(withReactQuery(ResourceReservationFormSection))),
'resourceReservationFormSection',
[
'namespaceHasQuota',
'resourceQuotaCapacityExceeded',
'maxMemoryLimit',
'maxCpuLimit',
],
resourceReservationValidation
);

@ -432,102 +432,15 @@
</div>
<!-- #endregion -->
<div class="col-sm-12 form-section-title"> Resource reservations </div>
<!-- #region RESOURCE RESERVATIONS -->
<div class="form-group" ng-if="!ctrl.state.resourcePoolHasQuota">
<div class="col-sm-12 small text-muted vertical-center">
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
Resource reservations are applied per instance of the application.
</div>
</div>
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && !ctrl.resourceQuotaCapacityExceeded()">
<div class="col-sm-12 small text-muted vertical-center">
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
A resource quota is set on this namespace, you must specify resource reservations. Resource reservations are applied per instance of the application. Maximums
are inherited from the namespace quota.
</div>
</div>
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded()">
<div class="col-sm-12 small text-muted vertical-center">
<pr-icon icon="'alert-circle'" mode="'danger'"></pr-icon>
This namespace has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of
the namespace.
</div>
</div>
<!-- memory-limit-input -->
<div
class="form-group flex"
ng-if="
(!ctrl.state.resourcePoolHasQuota || (ctrl.state.resourcePoolHasQuota && !ctrl.resourceQuotaCapacityExceeded())) && ctrl.formValues.Containers.length <= 1
"
>
<label for="memory-limit" class="space-right col-sm-3 col-lg-2 control-label flex flex-row items-center text-left">
Memory limit (MB)
<portainer-tooltip
message="'An instance of this application will reserve this amount of memory. If the instance memory usage exceeds the reservation, it might be subject to OOM.'"
>
</portainer-tooltip>
</label>
<div class="col-sm-6">
<slider model="ctrl.formValues.MemoryLimit" floor="ctrl.state.sliders.memory.min" ceil="ctrl.state.sliders.memory.max" step="128"></slider>
</div>
<div class="col-sm-2 vertical-center">
<input
name="memory_limit"
ng-model="ctrl.formValues.MemoryLimit"
type="number"
min="{{ ctrl.state.sliders.memory.min }}"
max="{{ ctrl.state.sliders.memory.max }}"
class="form-control"
id="memory-limit"
required
data-cy="k8sAppCreate-memoryLimit"
/>
</div>
</div>
<div class="form-group" ng-show="kubernetesApplicationCreationForm.memory_limit.$invalid">
<div class="col-sm-3 col-lg-2"></div>
<div class="col-sm-8 small text-warning">
<div ng-messages="kubernetesApplicationCreationForm.memory_limit.$error">
<p class="vertical-center"
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Value must be between {{ ctrl.state.sliders.memory.min }} and
{{ ctrl.state.sliders.memory.max }}
</p>
</div>
</div>
</div>
<!-- !memory-limit-input -->
<!-- cpu-limit-input -->
<div
class="form-group flex"
ng-if="
(!ctrl.state.resourcePoolHasQuota || (ctrl.state.resourcePoolHasQuota && !ctrl.resourceQuotaCapacityExceeded())) && ctrl.formValues.Containers.length <= 1
"
>
<label for="cpu-limit" class="space-right col-sm-3 col-lg-2 control-label flex flex-row items-center text-left">
CPU limit
<portainer-tooltip
message="'An instance of this application will reserve this amount of CPU. If the instance CPU usage exceeds the reservation, it might be subject to CPU throttling.'"
>
</portainer-tooltip>
</label>
<div class="col-sm-8">
<slider model="ctrl.formValues.CpuLimit" floor="ctrl.state.sliders.cpu.min" ceil="ctrl.state.sliders.cpu.max" step="0.10" precision="2"></slider>
</div>
</div>
<div class="form-group" ng-if="ctrl.nodeLimitsOverflow()">
<div class="col-sm-3 col-lg-2"></div>
<div class="col-sm-8 small text-muted">
<pr-icon icon="'alert-circle'" mode="'danger'"></pr-icon>
These reservations would exceed the resources currently available in the cluster.
</div>
</div>
<!-- !cpu-limit-input -->
<!-- #endregion -->
<resource-reservation-form-section
values="{memoryLimit: ctrl.formValues.MemoryLimit, cpuLimit: ctrl.formValues.CpuLimit}"
on-change="(ctrl.onChangeResourceReservation)"
namespace-has-quota="ctrl.state.resourcePoolHasQuota"
max-memory-limit="ctrl.state.sliders.memory.max"
max-cpu-limit="ctrl.state.sliders.cpu.max"
validation-data="{maxMemoryLimit: ctrl.state.sliders.memory.max, maxCpuLimit: ctrl.state.sliders.cpu.max}"
resource-quota-capacity-exceeded="ctrl.resourceQuotaCapacityExceeded()"
></resource-reservation-form-section>
<div class="col-sm-12 form-section-title"> Deployment </div>
<!-- #region DEPLOYMENT -->

@ -154,6 +154,7 @@ class KubernetesCreateApplicationController {
this.onConfigMapsChange = this.onConfigMapsChange.bind(this);
this.onSecretsChange = this.onSecretsChange.bind(this);
this.onChangePersistedFolder = this.onChangePersistedFolder.bind(this);
this.onChangeResourceReservation = this.onChangeResourceReservation.bind(this);
}
/* #endregion */
@ -539,6 +540,13 @@ class KubernetesCreateApplicationController {
}
}
onChangeResourceReservation(values) {
return this.$async(async () => {
this.formValues.MemoryLimit = values.memoryLimit;
this.formValues.CpuLimit = values.cpuLimit;
});
}
resourceQuotaCapacityExceeded() {
return !this.state.sliders.memory.max || !this.state.sliders.cpu.max;
}

@ -0,0 +1,99 @@
import { FormikErrors } from 'formik';
import { FormSection } from '@@/form-components/FormSection';
import { TextTip } from '@@/Tip/TextTip';
import { SliderWithInput } from '@@/form-components/Slider/SliderWithInput';
import { FormControl } from '@@/form-components/FormControl';
import { FormError } from '@@/form-components/FormError';
import { Slider } from '@@/form-components/Slider';
import { ResourceQuotaFormValues } from './types';
type Props = {
values: ResourceQuotaFormValues;
onChange: (values: ResourceQuotaFormValues) => void;
errors: FormikErrors<ResourceQuotaFormValues>;
namespaceHasQuota: boolean;
resourceQuotaCapacityExceeded: boolean;
maxMemoryLimit: number;
maxCpuLimit: number;
};
export function ResourceReservationFormSection({
values,
onChange,
errors,
namespaceHasQuota,
resourceQuotaCapacityExceeded,
maxMemoryLimit,
maxCpuLimit,
}: Props) {
return (
<FormSection title="Resource reservations" titleSize="md">
{!namespaceHasQuota && (
<TextTip color="blue">
Resource reservations are applied per instance of the application.
</TextTip>
)}
{namespaceHasQuota && !resourceQuotaCapacityExceeded && (
<TextTip color="blue">
A resource quota is set on this namespace, you must specify resource
reservations. Resource reservations are applied per instance of the
application. Maximums are inherited from the namespace quota.
</TextTip>
)}
{namespaceHasQuota && resourceQuotaCapacityExceeded && (
<TextTip color="red">
This namespace has exhausted its resource capacity and you will not be
able to deploy the application. Contact your administrator to expand
the capacity of the namespace.
</TextTip>
)}
<FormControl
className="flex flex-row"
label="Memory limit (MB)"
tooltip="An instance of this application will reserve this amount of memory. If the instance memory usage exceeds the reservation, it might be subject to OOM."
>
<div className="col-xs-10">
<SliderWithInput
value={Number(values.memoryLimit) ?? 0}
onChange={(value) => onChange({ ...values, memoryLimit: value })}
max={maxMemoryLimit}
step={128}
dataCy="k8sAppCreate-memoryLimit"
visibleTooltip
/>
{errors?.memoryLimit && (
<FormError className="pt-1">{errors.memoryLimit}</FormError>
)}
</div>
</FormControl>
<FormControl
className="flex flex-row"
label="CPU limit"
tooltip="An instance of this application will reserve this amount of CPU. If the instance CPU usage exceeds the reservation, it might be subject to CPU throttling."
>
<div className="col-xs-10">
<Slider
onChange={(value) =>
onChange(
typeof value === 'number'
? { ...values, cpuLimit: value }
: { ...values, cpuLimit: value[0] ?? 0 }
)
}
value={values.cpuLimit}
min={0}
max={maxCpuLimit}
step={0.01}
dataCy="k8sAppCreate-cpuLimitSlider"
visibleTooltip
/>
{errors?.cpuLimit && (
<FormError className="pt-1">{errors.cpuLimit}</FormError>
)}
</div>
</FormControl>
</FormSection>
);
}

@ -0,0 +1,2 @@
export { ResourceReservationFormSection } from './ResourceReservationFormSection';
export { resourceReservationValidation } from './resourceReservationValidation';

@ -0,0 +1,29 @@
import { SchemaOf, number, object } from 'yup';
import { ResourceQuotaFormValues } from './types';
type ValidationData = {
maxMemoryLimit: number;
maxCpuLimit: number;
};
export function resourceReservationValidation(
validationData?: ValidationData
): SchemaOf<ResourceQuotaFormValues> {
return object().shape({
memoryLimit: number()
.min(0)
.max(
validationData?.maxMemoryLimit || 0,
`Value must be between 0 and ${validationData?.maxMemoryLimit}`
)
.required(),
cpuLimit: number()
.min(0)
.max(
validationData?.maxCpuLimit || 0,
`Value must be between 0 and ${validationData?.maxCpuLimit}`
)
.required(),
});
}

@ -0,0 +1,4 @@
export type ResourceQuotaFormValues = {
memoryLimit: number;
cpuLimit: number;
};
Loading…
Cancel
Save