diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index f8c8a245eb..56e4ba0689 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -5cfc0dc6760ea7f631fc98ea1edd39adfc55bf11 +a2a23aebcf91985778fb632a415a6990d022f7c8 diff --git a/backend/windmill-api-configs/src/lib.rs b/backend/windmill-api-configs/src/lib.rs index cfa74bd2f0..8336888088 100644 --- a/backend/windmill-api-configs/src/lib.rs +++ b/backend/windmill-api-configs/src/lib.rs @@ -35,6 +35,7 @@ pub fn global_service() -> Router { "/list_autoscaling_events/{worker_group}", get(list_autoscaling_events), ) + .route("/test_autoscaling/{worker_group}", post(test_autoscaling)) .route( "/native_kubernetes_autoscaling_healthcheck", get(native_kubernetes_autoscaling_healthcheck), @@ -419,6 +420,48 @@ async fn native_kubernetes_autoscaling_healthcheck() -> Result<(), error::Error> )) } +#[derive(Serialize)] +struct TestAutoscalingResponse { + job_id: Option, +} + +#[cfg(all(feature = "enterprise", feature = "private"))] +async fn test_autoscaling( + authed: ApiAuthed, + Extension(db): Extension, + Path(worker_group): Path, + Json(test): Json, +) -> error::JsonResult { + require_devops_role(&db, &authed).await?; + audit_log( + &db, + &authed, + "worker_config.test_autoscaling", + ActionKind::Execute, + "global", + Some(&worker_group), + None, + ) + .await?; + let job_id = windmill_autoscaling::autoscaling_ee::test_autoscaling( + &db, + &worker_group, + test, + &authed.email, + ) + .await?; + Ok(Json(TestAutoscalingResponse { + job_id: job_id.map(|id| id.to_string()), + })) +} + +#[cfg(not(all(feature = "enterprise", feature = "private")))] +async fn test_autoscaling() -> error::JsonResult { + Err(error::Error::BadRequest( + "Autoscaling available only in the enterprise version".to_string(), + )) +} + async fn list_available_python_versions() -> error::JsonResult> { #[cfg(not(all(feature = "python", feature = "run_inline")))] return Err(error::Error::BadRequest( diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index fd5e02bed4..777209eda7 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -24649,6 +24649,56 @@ paths: items: $ref: "#/components/schemas/AutoscalingEvent" + /configs/test_autoscaling/{worker_group}: + post: + summary: Run an autoscaling integration once for a worker group, without recording an autoscaling event + operationId: testAutoscaling + tags: + - config + parameters: + - name: worker_group + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + desired_workers: + type: integer + minimum: 0 + integration: + description: The integration to run, as in the worker group's autoscaling config + type: object + properties: + type: + type: string + enum: [script, kubernetes, dryrun] + path: + type: string + tag: + type: string + required: + - type + required: + - desired_workers + - integration + responses: + "200": + description: The job queued for a script integration + content: + application/json: + schema: + type: object + properties: + job_id: + type: string + nullable: true + /configs/native_kubernetes_autoscaling_healthcheck: get: summary: Check Kubernetes autoscaling health for a worker group diff --git a/frontend/src/lib/components/AutoscalingConfigEditor.svelte b/frontend/src/lib/components/AutoscalingConfigEditor.svelte index 885b6dd55c..a8884657cd 100644 --- a/frontend/src/lib/components/AutoscalingConfigEditor.svelte +++ b/frontend/src/lib/components/AutoscalingConfigEditor.svelte @@ -16,8 +16,12 @@ import ScriptPicker from './ScriptPicker.svelte' import { sendUserToast } from '$lib/toast' import { untrack } from 'svelte' + import ConfirmationModal from './common/confirmationModal/ConfirmationModal.svelte' + import { createAsyncConfirmationModal } from './common/confirmationModal/asyncConfirmationModal.svelte' + import Portal from './Portal.svelte' interface Props { + worker_group: string config: AutoscalingConfig | undefined worker_tags: string[] | undefined /** Whether the draft config gives the group any tags (worker_tags or dedicated workers). */ @@ -29,6 +33,7 @@ } let { + worker_group, config = $bindable(), worker_tags, group_has_tags, @@ -66,6 +71,54 @@ } } + const confirmationModal = createAsyncConfirmationModal() + + async function testScaling() { + const integration = config?.integration + if (!integration) return + if (integration.type === 'script' && !integration['path']) { + sendUserToast('Please select a script before testing scaling', true) + return + } + const desired_workers = test_input + let job_id: string | null | undefined + let failed = false + const confirmed = await confirmationModal.ask({ + title: `Scale ${worker_group} to ${desired_workers} workers?`, + confirmationText: 'Scale now', + type: 'reload', + children: + integration.type === 'kubernetes' + ? `This sets the worker group's replica count to ${desired_workers} now. If autoscaling is enabled, it adjusts the count again on its next pass.` + : `This runs the integration script once with desired_workers set to ${desired_workers}, and it scales the group if the script does. If autoscaling is enabled, it adjusts the count again on its next pass.`, + onConfirmed: async () => { + try { + job_id = ( + await ConfigService.testAutoscaling({ + workerGroup: worker_group, + requestBody: { desired_workers, integration: $state.snapshot(integration) } + }) + ).job_id + } catch (e: any) { + failed = true + sendUserToast('Test scaling failed', true, [], e?.body ?? e?.message) + } + } + }) + if (!confirmed || failed) return + if (job_id) { + const id = job_id + sendUserToast('Autoscaling script queued in the admins workspace', false, [ + { + label: 'See job', + callback: () => window.open('/run/' + id + '?workspace=admins', '_blank') + } + ]) + } else { + sendUserToast(`Scaled ${worker_group} to ${desired_workers} workers`) + } + } + let noTagsToCount = $derived(!!config?.enabled && !group_has_tags && !config.custom_tags?.length) let collapsed: boolean = $state(untrack(() => !noTagsToCount)) @@ -266,22 +319,11 @@ />
- Test scaling
- + workers
@@ -317,9 +359,11 @@
- +
- + workers
@@ -547,3 +591,7 @@ + + + + diff --git a/frontend/src/lib/components/WorkerGroup.svelte b/frontend/src/lib/components/WorkerGroup.svelte index 584d3bf2d5..3ee0807c50 100644 --- a/frontend/src/lib/components/WorkerGroup.svelte +++ b/frontend/src/lib/components/WorkerGroup.svelte @@ -1078,6 +1078,7 @@
0