refactor(app): migrate-autoscaling [EE-6387] (#10709)

* refactor(app): migrate-autoscaling [EE-6387]
pull/10818/head
Ali 11 months ago committed by GitHub
parent 6da71661d5
commit 2d77e71085
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -38,6 +38,10 @@ import {
ReplicationFormSection,
replicationValidation,
} from '@/react/kubernetes/applications/components/ReplicationFormSection';
import {
AutoScalingFormSection,
autoScalingValidation,
} from '@/react/kubernetes/applications/components/AutoScalingFormSection';
import { EnvironmentVariablesFieldset } from '@@/form-components/EnvironmentVariablesFieldset';
@ -255,3 +259,11 @@ withFormValidation(
],
replicationValidation
);
withFormValidation(
ngModule,
withUIRouter(withCurrentUser(withReactQuery(AutoScalingFormSection))),
'autoScalingFormSection',
['isMetricsEnabled'],
autoScalingValidation
);

@ -466,128 +466,13 @@
<!-- #endregion -->
<!-- #region AUTO SCALING -->
<div class="form-group !mb-0" ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && !ctrl.state.useServerMetrics">
<div class="col-sm-12 small text-muted">
<p ng-if="!ctrl.isAdmin"> This feature is currently disabled and must be enabled by an administrator user. </p>
<p ng-if="ctrl.isAdmin">
Server metrics features must be enabled in the
<a ui-sref="kubernetes.cluster.setup" class="ctrl.isAdmin">environment configuration view</a>.
</p>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<div class="col-sm-3 col-lg-2 pl-0 pt-0">
<label for="enable_auto_scaling" class="control-label text-left"> Enable auto scaling for this application </label>
</div>
<label class="switch ml-4 mt-1">
<input
type="checkbox"
class="form-control"
name="enable_auto_scaling"
ng-model="ctrl.formValues.AutoScaler.IsUsed"
data-cy="k8sAppCreate-autoScaleCheckbox"
ng-disabled="!(ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && ctrl.state.useServerMetrics)"
/>
<span class="slider round"></span>
</label>
</div>
</div>
<div class="form-inline" ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && ctrl.formValues.AutoScaler.IsUsed">
<div class="row">
<div class="col-sm-4 pl-0">
<label class="control-label pb-2 text-left" for="auto_scaler_min">Minimum instances</label>
<div class="input-group input-group-sm" style="width: 100%">
<input
type="number"
class="form-control"
name="auto_scaler_min"
min="0"
ng-max="ctrl.formValues.AutoScaler.MaxReplicas"
ng-model="ctrl.formValues.AutoScaler.MinReplicas"
data-cy="k8sAppCreate-autoScaleMin"
required
/>
</div>
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_min'].$invalid">
<div class="small text-warning" style="margin-top: 5px">
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_min'].$error">
<p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Minimum instances is required. </p>
<p ng-message="min" class="vertical-center">
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Minimum instances must be greater than 0.
</p>
<p ng-message="max" class="vertical-center">
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Minimum instances must be smaller than maximum instances.
</p>
</ng-messages>
</div>
</span>
</div>
<div class="col-sm-4 pl-0">
<label class="control-label pb-2 text-left" for="auto_scaler_max">Maximum instances</label>
<div class="input-group input-group-sm" style="width: 100%">
<input
type="number"
class="form-control"
name="auto_scaler_max"
ng-min="ctrl.formValues.AutoScaler.MinReplicas"
ng-model="ctrl.formValues.AutoScaler.MaxReplicas"
/>
</div>
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_max'].$invalid || ctrl.autoScalerOverflow()">
<div class="small text-warning" style="margin-top: 5px">
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_max'].$error">
<p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Maximum instances is required. </p>
<p ng-message="min" class="vertical-center">
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Maximum instances must be greater than minimum instances.
</p>
</ng-messages>
</div>
</span>
</div>
<div class="col-sm-4 pl-0">
<label class="control-label pb-2 text-left" for="auto_scaler_cpu">
Target CPU usage (<b>%</b>)
<portainer-tooltip message="'The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances.'">
</portainer-tooltip>
</label>
<div class="input-group input-group-sm" style="width: 100%">
<input
type="number"
class="form-control"
name="auto_scaler_cpu"
ng-model="ctrl.formValues.AutoScaler.TargetCPUUtilization"
min="1"
max="100"
required
data-cy="k8sAppCreate-targetCPUInput"
/>
</div>
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_cpu'].$invalid">
<div class="small text-warning" style="margin-top: 5px">
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_cpu'].$error">
<p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Target CPU usage is required. </p>
<p ng-message="min" class="vertical-center">
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Target CPU usage must be greater than 0.
</p>
<p ng-message="max" class="vertical-center">
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Target CPU usage must be smaller than 100.
</p>
</ng-messages>
</div>
</span>
</div>
</div>
<div class="form-group" ng-if="ctrl.autoScalerOverflow()" style="margin-bottom: 10px">
<div class="col-sm-12 small text-danger">
<pr-icon icon="'alert-circle'" mode="'danger'"></pr-icon>
This application would exceed available resources. Please review resource reservations or the maximum instance count of the auto-scaling policy.
</div>
</div>
<div ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL">
<auto-scaling-form-section
values="{isUsed: ctrl.formValues.AutoScaler.IsUsed, minReplicas: ctrl.formValues.AutoScaler.MinReplicas, maxReplicas: ctrl.formValues.AutoScaler.MaxReplicas, targetCpuUtilizationPercentage: ctrl.formValues.AutoScaler.TargetCPUUtilization}"
on-change="(ctrl.onAutoScaleChange)"
validation-data="{autoScalerOverflow: ctrl.autoScalerOverflow()}"
is-metrics-enabled="ctrl.state.useServerMetrics"
></auto-scaling-form-section>
</div>
<!-- #endregion -->

@ -156,6 +156,7 @@ class KubernetesCreateApplicationController {
this.onChangePersistedFolder = this.onChangePersistedFolder.bind(this);
this.onChangeResourceReservation = this.onChangeResourceReservation.bind(this);
this.onChangeReplicaCount = this.onChangeReplicaCount.bind(this);
this.onAutoScaleChange = this.onAutoScaleChange.bind(this);
}
/* #endregion */
@ -238,6 +239,26 @@ class KubernetesCreateApplicationController {
this.formValues.AutoScaler.IsUsed = false;
}
}
onAutoScaleChange(values) {
return this.$async(async () => {
if (!this.formValues.AutoScaler.IsUsed && values.isUsed) {
this.formValues.AutoScaler = {
IsUsed: values.isUsed,
MinReplicas: 1,
MaxReplicas: 3,
TargetCPUUtilization: 50,
};
return;
}
this.formValues.AutoScaler = {
IsUsed: values.isUsed,
MinReplicas: values.minReplicas,
MaxReplicas: values.maxReplicas,
TargetCPUUtilization: values.targetCpuUtilizationPercentage,
};
});
}
/* #endregion */
/* #region CONFIGMAP UI MANAGEMENT */

@ -0,0 +1,132 @@
import { FormikErrors } from 'formik';
import { useCurrentUser } from '@/react/hooks/useUser';
import { SwitchField } from '@@/form-components/SwitchField';
import { Link } from '@@/Link';
import { TextTip } from '@@/Tip/TextTip';
import { Input } from '@@/form-components/Input';
import { FormError } from '@@/form-components/FormError';
import { Tooltip } from '@@/Tip/Tooltip';
import { AutoScalingFormValues } from './types';
type Props = {
values: AutoScalingFormValues;
onChange: (values: AutoScalingFormValues) => void;
errors: FormikErrors<AutoScalingFormValues>;
isMetricsEnabled: boolean;
};
export function AutoScalingFormSection({
values,
onChange,
errors,
isMetricsEnabled,
}: Props) {
return (
<>
{!isMetricsEnabled && <NoMetricsServerWarning />}
<SwitchField
disabled={!isMetricsEnabled}
label="Enable auto scaling for this application"
labelClass="col-sm-3 col-lg-2"
checked={values.isUsed}
onChange={(value: boolean) =>
onChange({
...values,
isUsed: value,
})
}
/>
{values.isUsed && (
<div className="grid grid-cols-1 md:grid-cols-3 w-full gap-x-4 gap-y-2 my-3">
<div className="flex flex-col min-w-fit">
<label htmlFor="min-instances" className="font-normal text-xs">
Minimum instances
</label>
<Input
id="min-instances"
type="number"
min="0"
value={values.minReplicas}
max={values.maxReplicas || 1}
onChange={(e) =>
onChange({
...values,
minReplicas: Number(e.target.value) || 0,
})
}
data-cy="k8sAppCreate-autoScaleMin"
/>
{errors?.minReplicas && <FormError>{errors.minReplicas}</FormError>}
</div>
<div className="flex flex-col min-w-fit">
<label htmlFor="max-instances" className="font-normal text-xs">
Maximum instances
</label>
<Input
id="max-instances"
type="number"
value={values.maxReplicas}
min={values.minReplicas || 1}
onChange={(e) =>
onChange({
...values,
maxReplicas: Number(e.target.value) || 1,
})
}
data-cy="k8sAppCreate-autoScaleMax"
/>
{errors?.maxReplicas && <FormError>{errors.maxReplicas}</FormError>}
</div>
<div className="flex flex-col min-w-fit">
<label
htmlFor="cpu-threshold"
className="font-normal text-xs flex items-center"
>
Target CPU usage (<b>%</b>)
<Tooltip message="The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances." />
</label>
<Input
id="cpu-threshold"
type="number"
value={values.targetCpuUtilizationPercentage}
min="1"
max="100"
onChange={(e) =>
onChange({
...values,
targetCpuUtilizationPercentage: Number(e.target.value) || 1,
})
}
data-cy="k8sAppCreate-targetCPUInput"
/>
{errors?.targetCpuUtilizationPercentage && (
<FormError>{errors.targetCpuUtilizationPercentage}</FormError>
)}
</div>
</div>
)}
</>
);
}
function NoMetricsServerWarning() {
const { isAdmin } = useCurrentUser();
return (
<TextTip color="orange">
{isAdmin && (
<>
Server metrics features must be enabled in the{' '}
<Link to="kubernetes.cluster.setup">
environment configuration view
</Link>
.
</>
)}
{!isAdmin &&
'This feature is currently disabled and must be enabled by an administrator user.'}
</TextTip>
);
}

@ -0,0 +1,69 @@
import { SchemaOf, boolean, number, object } from 'yup';
import { AutoScalingFormValues } from './types';
type ValidationData = {
autoScalerOverflow: boolean;
};
export function autoScalingValidation(
validationData?: ValidationData
): SchemaOf<AutoScalingFormValues> {
const { autoScalerOverflow } = validationData || {};
return object({
isUsed: boolean().required(),
minReplicas: number()
.min(0, 'Minimum instances must be greater than 0.')
.when('isUsed', (isUsed: boolean) =>
isUsed
? number()
.required('Minimum instances is required.')
.test(
'maxReplicas',
'Minimum instances must be less than maximum instances.',
// eslint-disable-next-line func-names
function (this, value?: number): boolean {
if (!value) {
return false;
}
const { maxReplicas } = this.parent as AutoScalingFormValues;
return !maxReplicas || value < maxReplicas;
}
)
: number()
),
maxReplicas: number().when('isUsed', (isUsed: boolean) =>
isUsed
? number()
.required('Maximum instances is required.')
.test(
'minReplicas',
'Maximum instances must be greater than minimum instances.',
// eslint-disable-next-line func-names
function (this, value?: number): boolean {
if (!value) {
return false;
}
const { minReplicas } = this.parent as AutoScalingFormValues;
return !minReplicas || value > minReplicas;
}
)
.test(
'overflow',
'This application would exceed available resources. Please reduce the maximum instances or the resource reservations.',
() => !autoScalerOverflow
)
: number()
),
targetCpuUtilizationPercentage: number().when(
'isUsed',
(isUsed: boolean) =>
isUsed
? number()
.min(0, 'Target CPU usage must be greater than 0.')
.max(100, 'Target CPU usage must be smaller than 100.')
.required('Target CPU utilization percentage is required.')
: number()
),
});
}

@ -0,0 +1,2 @@
export { AutoScalingFormSection } from './AutoScalingFormSection';
export { autoScalingValidation } from './autoScalingValidation';

@ -0,0 +1,6 @@
export type AutoScalingFormValues = {
isUsed: boolean;
minReplicas?: number;
maxReplicas?: number;
targetCpuUtilizationPercentage?: number;
};
Loading…
Cancel
Save