Skip to content

fix: normalize annotation side of validateDeploymentMode before compare - #6069

Merged
spolti merged 1 commit into
kserve:masterfrom
UgaTheDev:fix-deployment-mode-alias-validation
Sep 30, 2026
Merged

spolti merged 1 commit into
kserve:masterfrom
UgaTheDev:fix-deployment-mode-alias-validation

Conversation

@UgaTheDev

Copy link
Copy Markdown
Contributor

Fixes #5885

Problem

validateDeploymentMode normalizes oldIsvc.Status.DeploymentMode through
constants.ParseDeploymentMode but compares it against the raw
newIsvc.Annotations[constants.DeploymentMode] string. Legacy alias
annotations (Serverless / RawDeployment) never normalize-equal their raw
form, so once the controller records the normalized status
(Knative / Standard), every subsequent update — including a completely
unchanged kubectl apply — is rejected:

update rejected: deploymentMode cannot be changed from 'Knative' to 'Serverless'

On versions without the DeletionTimestamp bypass in ValidateUpdate
(v0.16.x), this also blocks the finalizer-removal update the controller
issues during deletion, so the InferenceService deadlocks in Terminating
and no kubectl patch can remove the finalizer either, since the same
webhook path validates that update too.

This is the sibling of #5793/#5799: that pair fixed the empty status case
of the same comparison; this is the legacy alias case, left over because
only the status side was ever normalized.

Fix

Normalize the annotation the same way the status side already is, before
comparing:

rawAnnotationDeploymentMode, ok := newIsvc.Annotations[constants.DeploymentMode]
annotationDeploymentMode := string(constants.ParseDeploymentMode(rawAnnotationDeploymentMode))
if ok && annotationDeploymentMode != statusDeploymentMode {
    return fmt.Errorf("update rejected: deploymentMode cannot be changed from '%s' to '%s'", statusDeploymentMode, rawAnnotationDeploymentMode)
}

The error message still reports the raw annotation string (what the user
actually wrote), only the comparison itself is normalized. A real mode
change (e.g. legacy status Standard -> legacy annotation Serverless) is
still rejected — only a semantically-unchanged legacy alias now passes.

Sibling pattern found (not fixed here, flagging for maintainers)

validateCanarySpecs (same file, ~line 188) has the identical class of bug:

deploymentMode := isvc.Annotations[constants.DeploymentMode]
if deploymentMode != string(constants.Standard) {
    return fmt.Errorf("canary is only supported in %s deployment mode", constants.Standard)
}

This compares the raw annotation directly against the literal string
"Standard", so an InferenceService using the legacy RawDeployment
annotation (which is semantically Standard) would have its canary spec
rejected even though the deployment mode is correct. Left out of this PR to
keep the diff focused on the reported issue and its exact repro — happy to
open a follow-up if maintainers agree it's in scope.

Testing

Added regression cases to TestDeploymentModeUpdate in
inference_service_validation_test.go:

  • unchanged legacy Serverless annotation against normalized Knative
    status -> now accepted (previously rejected, reproduced the fail-before
    the fix)
  • unchanged legacy RawDeployment annotation against normalized Standard
    status -> now accepted
  • a legacy alias annotation that actually changes the mode -> still rejected
    (guards against over-normalizing away real change detection)
$ go test ./pkg/apis/serving/v1beta1/... -run TestDeploymentModeUpdate -v
=== RUN   TestDeploymentModeUpdate
--- PASS: TestDeploymentModeUpdate (0.00s)
PASS
ok  	github.com/kserve/kserve/pkg/apis/serving/v1beta1	2.714s

$ go test ./pkg/apis/serving/v1beta1/...
ok  	github.com/kserve/kserve/pkg/apis/serving/v1beta1	1.447s

$ go vet ./pkg/apis/serving/v1beta1/...
(clean)

Before the fix, the added test failed with exactly the error from the issue
report:

update rejected: deploymentMode cannot be changed from 'Knative' to 'Serverless'

I have not reproduced the deletion-deadlock symptom itself end-to-end
(that needs envtest/a real cluster on a v0.16.x build without the
DeletionTimestamp bypass); the unit tests cover the underlying comparison
bug that causes it.

@github-actions
github-actions Bot requested review from Datta0 and pierDipi August 24, 2026 18:40
@UgaTheDev UgaTheDev changed the title fix: normalize the annotation side of validateDeploymentMode before comparing fix: normalize annotation side of validateDeploymentMode before compare Aug 25, 2026
@UgaTheDev

Copy link
Copy Markdown
Contributor Author

@pierDipi @spolti could one of you take a look when you get a chance? This fixes the deletion deadlock in #5885 — reproduced with a unit test against master (a legacy alias annotation like Serverless fails the mode comparison during finalizer processing and wedges deletion) — and the change is a one-function normalization plus the regression test.

@spolti

spolti commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Thanks @UgaTheDev!

@spolti

spolti commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

/lgtm
/approve

@github-actions github-actions Bot added the lgtm label Sep 12, 2026
@spolti

spolti commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

@UgaTheDev can you please rebase your PR?

…DeploymentMode

validateDeploymentMode normalized the status side through
ParseDeploymentMode but compared it against the raw annotation string.
Legacy alias annotations (Serverless/RawDeployment) never normalize-equal
their raw form, so an unchanged update using a legacy alias annotation was
rejected once status.deploymentMode was normalized. On versions lacking
the DeletionTimestamp bypass in ValidateUpdate this also deadlocks
deletion, since finalizer removal goes through the same validation.

Fixes kserve#5885

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EWug344hH7N5dRsGQMvuhJ
Signed-off-by: Kush Zingade <[email protected]>
@UgaTheDev
UgaTheDev force-pushed the fix-deployment-mode-alias-validation branch from 9e2a100 to bf1c6d1 Compare September 13, 2026 23:14
@github-actions github-actions Bot removed the lgtm label Sep 13, 2026
@UgaTheDev

Copy link
Copy Markdown
Contributor Author

Rebased onto master (bf1c6d1), no conflicts. go test ./pkg/apis/serving/v1beta1/ is green locally. Thanks @spolti!

@UgaTheDev

Copy link
Copy Markdown
Contributor Author

Friendly ping @pierDipi @Datta0: this fixes #5885, which the stale bot is about to close. CI is green and the branch is mergeable. Happy to adjust anything.

@spolti

spolti commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@github-actions github-actions Bot added the lgtm label Sep 30, 2026
@spolti
spolti merged commit be0b6e3 into kserve:master Sep 30, 2026
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants