Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions api/serving/v1alpha1/llmservice_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ const (
// newest chart the repository holds. Any later new value does it again.
AnnotationReResolve = "serving.modelsphere.dev/re-resolve"

// AnnotationSkipSchemaValidation, set to "true", installs and upgrades
// without checking the merged values against the chart's
// values.schema.json, as helm's --skip-schema-validation does. Any other
// value leaves the check on. Not part of the applied hash: turning it on
// re-applies only a release whose last apply did not succeed.
AnnotationSkipSchemaValidation = "serving.modelsphere.dev/skip-schema-validation"

// SwissAnnotationPrefix selects annotations copied into each history entry.
// The operator otherwise ignores them.
SwissAnnotationPrefix = "swiss.modelsphere.dev/"
Expand Down
1 change: 1 addition & 0 deletions docs/llmservice.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ A layer with no `values`, or `values: null`, contributes nothing.
| `serving.modelsphere.dev/force-delete-after` | Overrides `spec.deletion.forceDeleteAfter`. `0s` force-deletes a terminating pod on the next pass. |
| `serving.modelsphere.dev/force-conflicts: "<generation>"` | Force field-manager conflicts on the apply for that generation only. Any other value is ignored. Recorded as `forceConflicts` in `status.history`. |
| `serving.modelsphere.dev/re-resolve: "<any new value>"` | Drop the pinned chart version once and resolve `spec.chart.version` to the newest version in range. See [Chart version](#chart-version). |
| `serving.modelsphere.dev/skip-schema-validation: "true"` | Install and upgrade without checking the merged values against the chart's `values.schema.json` (helm's `--skip-schema-validation`), for every apply while set. Any other value leaves the check on. An escape hatch for values the schema refuses. Not part of the applied hash: turning it on re-applies only a release whose last apply did not succeed. |
| `swiss.modelsphere.dev/*` | Copied into each history entry and ControllerRevision. Otherwise ignored. |

Annotation changes trigger a reconcile, as do generation changes and deletion. Status-only updates do not.
Expand Down
2 changes: 2 additions & 0 deletions internal/controller/serving/llmservice_cases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ type expectSpec struct {
HelmTouched *int `json:"helmTouched,omitempty"`
ListCalls *int `json:"listCalls,omitempty"`
LastForce *bool `json:"lastForce,omitempty"`
LastSkipSchema *bool `json:"lastSkipSchemaValidation,omitempty"`
GetsUnchanged *bool `json:"getsUnchanged,omitempty"`
UpgradesUnchanged *bool `json:"upgradesUnchanged,omitempty"`
Phase string `json:"phase,omitempty"`
Expand Down Expand Up @@ -922,6 +923,7 @@ func (c *caseRun) checkHelm(exp *expectSpec, before helmSnap) {
Expect(c.h.gets + c.h.installs + c.h.upgrades).To(Equal(*exp.HelmTouched))
}
expectBool(c.h.lastForce, exp.LastForce)
expectBool(c.h.lastSkipSchema, exp.LastSkipSchema)
if exp.GetsUnchanged != nil && *exp.GetsUnchanged {
Expect(c.h.gets).To(Equal(before.gets))
}
Expand Down
7 changes: 4 additions & 3 deletions internal/controller/serving/llmservice_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,10 @@ func (r *LLMServiceReconciler) apply(ctx context.Context, obj *servingv1alpha1.L
}
ref := helm.ChartRef{Repo: obj.Spec.Chart.Repo, Name: obj.Spec.Chart.Name, Version: ver}
opts := helm.ApplyOpts{
ForceConflicts: obj.Annotations[servingv1alpha1.AnnotationForceConflicts] == strconv.FormatInt(obj.Generation, 10),
Credentials: creds,
Labels: releaseLabels(obj),
ForceConflicts: obj.Annotations[servingv1alpha1.AnnotationForceConflicts] == strconv.FormatInt(obj.Generation, 10),
SkipSchemaValidation: obj.Annotations[servingv1alpha1.AnnotationSkipSchemaValidation] == "true",
Credentials: creds,
Labels: releaseLabels(obj),
}
var (
rel *helm.Release
Expand Down
3 changes: 3 additions & 0 deletions internal/controller/serving/llmservice_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type fakeHelm struct {
upgradeErr error
getErr error
lastForce bool
lastSkipSchema bool
// keepFailed stores a failed release when Install returns installErr.
keepFailed bool
// hold blocks Install and Upgrade until closed. entered is signaled first.
Expand Down Expand Up @@ -170,6 +171,7 @@ func (f *fakeHelm) gate() {
func (f *fakeHelm) Install(_ context.Context, ns, name string, ch helm.ChartRef, values map[string]any, opts helm.ApplyOpts) (*helm.Release, error) {
f.installs++
f.lastForce = opts.ForceConflicts
f.lastSkipSchema = opts.SkipSchemaValidation
f.gate()
if f.installErr != nil {
if f.keepFailed {
Expand All @@ -192,6 +194,7 @@ func (f *fakeHelm) Install(_ context.Context, ns, name string, ch helm.ChartRef,
func (f *fakeHelm) Upgrade(_ context.Context, ns, name string, ch helm.ChartRef, values map[string]any, opts helm.ApplyOpts) (*helm.Release, error) {
f.upgrades++
f.lastForce = opts.ForceConflicts
f.lastSkipSchema = opts.SkipSchemaValidation
f.gate()
if f.upgradeErr != nil {
return nil, f.upgradeErr
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: applies-again-once-skip-schema-validation-is-turned-on
description: a release the chart's schema refused applies once the skip-schema-validation annotation is set, with no spec change
steps:
- action: setHelm
installErr: "values don't meet the specifications of the schema(s)"
- action: create
version: "0.8.6"
- action: reconcile
expect:
errorContains: schema
installs: 1
lastSkipSchemaValidation: false
- action: setHelm
installErr: ""
- action: update
annotations:
serving.modelsphere.dev/skip-schema-validation: "true"
expect:
generation: 1
- action: reconcile
expect:
installs: 2
lastSkipSchemaValidation: true
phase: Applied
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: passes-skip-schema-validation-to-helm
description: passes the skip-schema-validation annotation to the install and the upgrade it is set for; only "true" turns it on
steps:
- action: create
version: "0.8.6"
annotations:
serving.modelsphere.dev/skip-schema-validation: "true"
- action: reconcile
expect:
installs: 1
lastSkipSchemaValidation: true
phase: Applied
- action: update
annotations:
serving.modelsphere.dev/skip-schema-validation: "yes"
layers:
- name: site
values: '{"replicaCount":2}'
expect:
generation: 2
- action: reconcile
expect:
upgrades: 1
lastSkipSchemaValidation: false
7 changes: 6 additions & 1 deletion internal/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ type Credentials struct {
// ApplyOpts controls one install or upgrade.
type ApplyOpts struct {
ForceConflicts bool
Credentials *Credentials
// SkipSchemaValidation leaves the values unchecked against the chart's
// values.schema.json.
SkipSchemaValidation bool
Credentials *Credentials
// Labels are stored on the release by Install and Upgrade.
Labels map[string]string
}
Expand Down Expand Up @@ -180,6 +183,7 @@ func (c *Client) Install(ctx context.Context, ns, releaseName string, ch ChartRe
inst.Namespace = ns
inst.ServerSideApply = true
inst.ForceConflicts = opts.ForceConflicts
inst.SkipSchemaValidation = opts.SkipSchemaValidation
inst.WaitStrategy = kube.HookOnlyStrategy
inst.Timeout = ApplyTimeout
inst.Labels = maps.Clone(opts.Labels)
Expand Down Expand Up @@ -209,6 +213,7 @@ func (c *Client) Upgrade(ctx context.Context, ns, releaseName string, ch ChartRe
up.Namespace = ns
up.ServerSideApply = "true"
up.ForceConflicts = opts.ForceConflicts
up.SkipSchemaValidation = opts.SkipSchemaValidation
up.WaitStrategy = kube.HookOnlyStrategy
up.Timeout = ApplyTimeout
up.Labels = maps.Clone(opts.Labels)
Expand Down
Loading