Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
OpenDAS
dynamo
Commits
4f686262
Unverified
Commit
4f686262
authored
Feb 11, 2026
by
Julien Mancuso
Committed by
GitHub
Feb 11, 2026
Browse files
feat: include operator version in DGD (#6121)
Signed-off-by:
Julien Mancuso
<
jmancuso@nvidia.com
>
parent
5602dd2f
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
498 additions
and
11 deletions
+498
-11
deploy/helm/charts/platform/Chart.yaml
deploy/helm/charts/platform/Chart.yaml
+2
-2
deploy/helm/charts/platform/components/operator/Chart.yaml
deploy/helm/charts/platform/components/operator/Chart.yaml
+2
-2
deploy/helm/charts/platform/components/operator/templates/deployment.yaml
...ts/platform/components/operator/templates/deployment.yaml
+1
-1
deploy/helm/charts/platform/components/operator/templates/webhook-ca-inject-job.yaml
.../components/operator/templates/webhook-ca-inject-job.yaml
+25
-4
deploy/helm/charts/platform/components/operator/templates/webhook-configuration.yaml
.../components/operator/templates/webhook-configuration.yaml
+52
-0
deploy/operator/cmd/main.go
deploy/operator/cmd/main.go
+21
-0
deploy/operator/go.mod
deploy/operator/go.mod
+1
-1
deploy/operator/internal/consts/consts.go
deploy/operator/internal/consts/consts.go
+4
-0
deploy/operator/internal/dynamo/graph.go
deploy/operator/internal/dynamo/graph.go
+13
-1
deploy/operator/internal/webhook/defaulting/dynamographdeployment_handler.go
...ernal/webhook/defaulting/dynamographdeployment_handler.go
+93
-0
deploy/operator/internal/webhook/defaulting/dynamographdeployment_handler_test.go
.../webhook/defaulting/dynamographdeployment_handler_test.go
+167
-0
deploy/operator/internal/webhook/validation/dynamographdeployment.go
...ator/internal/webhook/validation/dynamographdeployment.go
+27
-0
deploy/operator/internal/webhook/validation/dynamographdeployment_test.go
...internal/webhook/validation/dynamographdeployment_test.go
+90
-0
No files found.
deploy/helm/charts/platform/Chart.yaml
View file @
4f686262
...
...
@@ -19,11 +19,11 @@ maintainers:
url
:
https://www.nvidia.com
description
:
A Helm chart for NVIDIA Dynamo Platform.
type
:
application
version
:
0.9.0
version
:
1.0.0-dev
home
:
https://nvidia.com
dependencies
:
-
name
:
dynamo-operator
version
:
0.7.1
version
:
1.0.0-dev
repository
:
file://components/operator
condition
:
dynamo-operator.enabled
-
name
:
nats
...
...
deploy/helm/charts/platform/components/operator/Chart.yaml
View file @
4f686262
...
...
@@ -27,9 +27,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version
:
0.7.1
version
:
1.0.0-dev
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion
:
"
0.7.1
"
appVersion
:
"
1.0.0-dev
"
deploy/helm/charts/platform/components/operator/templates/deployment.yaml
View file @
4f686262
...
...
@@ -143,7 +143,7 @@ spec:
-
--namespace-scope-lease-renew-interval={{ .Values.namespaceRestriction.lease.renewInterval }}
{{
- end
}}
{{
- end
}}
-
--operator-version={{
.Values.controllerManager.manager.image.tag | default
.Chart.AppVersion }}
-
--operator-version={{ .Chart.AppVersion }}
{{
- if .Values.webhook.enabled
}}
-
--enable-webhooks=true
{{
- end
}}
...
...
deploy/helm/charts/platform/components/operator/templates/webhook-ca-inject-job.yaml
View file @
4f686262
...
...
@@ -98,6 +98,14 @@ rules:
resourceNames:
["
{{
include "dynamo-operator.fullname" .
}}
-validating"
]
{{
- end
}}
verbs
:
[
"
get"
,
"
patch"
]
-
apiGroups
:
[
"
admissionregistration.k8s.io"
]
resources
:
[
"
mutatingwebhookconfigurations"
]
{{
- if .Values.namespaceRestriction.enabled
}}
resourceNames
:
[
"
{{
include
"
dynamo-operator.fullname" .
}}
-mutating-
{{
.Release.Namespace
}}
"
]
{{-
else
}}
resourceNames:
["
{{
include "dynamo-operator.fullname" .
}}
-mutating"
]
{{
- end
}}
verbs
:
[
"
get"
,
"
patch"
]
---
apiVersion
:
rbac.authorization.k8s.io/v1
kind
:
ClusterRoleBinding
...
...
@@ -176,9 +184,11 @@ spec:
SECRET_NAME="{{ .Values.webhook.certificateSecret.name }}"
NAMESPACE="{{ .Release.Namespace }}"
{{- if .Values.namespaceRestriction.enabled }}
WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-validating-{{ .Release.Namespace }}"
VALIDATING_WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-validating-{{ .Release.Namespace }}"
MUTATING_WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-mutating-{{ .Release.Namespace }}"
{{- else }}
WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-validating"
VALIDATING_WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-validating"
MUTATING_WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-mutating"
{{- end }}
echo "⏳ Waiting for certificate secret to be available..."
...
...
@@ -207,8 +217,8 @@ spec:
fi
echo "📝 Patching ValidatingWebhookConfiguration..."
# Patch all webhooks (DynamoComponentDeployment, DynamoGraphDeployment, DynamoModel, DynamoGraphDeploymentRequest)
kubectl patch validatingwebhookconfiguration ${WEBHOOK_NAME} \
# Patch all
validating
webhooks (DynamoComponentDeployment, DynamoGraphDeployment, DynamoModel, DynamoGraphDeploymentRequest)
kubectl patch validatingwebhookconfiguration ${
VALIDATING_
WEBHOOK_NAME} \
--type='json' -p="[
{
\"op\": \"add\",
...
...
@@ -232,6 +242,17 @@ spec:
}
]"
echo "📝 Patching MutatingWebhookConfiguration..."
# Patch mutating webhook (DynamoGraphDeployment defaulting)
kubectl patch mutatingwebhookconfiguration ${MUTATING_WEBHOOK_NAME} \
--type='json' -p="[
{
\"op\": \"add\",
\"path\": \"/webhooks/0/clientConfig/caBundle\",
\"value\": \"${CA_BUNDLE}\"
}
]"
echo "✅ CA bundle injected successfully!"
echo "🎉 Webhook configuration complete!"
securityContext
:
...
...
deploy/helm/charts/platform/components/operator/templates/webhook-configuration.yaml
View file @
4f686262
...
...
@@ -169,5 +169,57 @@ webhooks:
-
dynamographdeploymentrequests
sideEffects
:
None
timeoutSeconds
:
{{
.Values.webhook.timeoutSeconds
}}
---
apiVersion
:
admissionregistration.k8s.io/v1
kind
:
MutatingWebhookConfiguration
metadata
:
{{
- if .Values.namespaceRestriction.enabled
}}
name
:
{{
include "dynamo-operator.fullname" .
}}
-mutating-{{ .Release.Namespace }}
{{
- else
}}
name
:
{{
include "dynamo-operator.fullname" .
}}
-mutating
{{
- end
}}
labels
:
app.kubernetes.io/component
:
webhook
app.kubernetes.io/created-by
:
dynamo-operator
app.kubernetes.io/part-of
:
dynamo-operator
{{
- include "dynamo-operator.labels" . | nindent 4
}}
{{
- if .Values.webhook.certManager.enabled
}}
annotations
:
cert-manager.io/inject-ca-from
:
{{
.Release.Namespace
}}
/{{ include "dynamo-operator.fullname" . }}-serving-cert
{{
- end
}}
webhooks
:
-
admissionReviewVersions
:
-
v1
clientConfig
:
{{
- if and (not .Values.webhook.certManager.enabled) .Values.webhook.certificateSecret.external
}}
{{
- if .Values.webhook.caBundle
}}
caBundle
:
{{
.Values.webhook.caBundle
}}
{{
- end
}}
{{
- end
}}
service
:
name
:
{{
include "dynamo-operator.fullname" .
}}
-webhook-service
namespace
:
{{
.Release.Namespace
}}
path
:
/mutate-nvidia-com-v1alpha1-dynamographdeployment
failurePolicy
:
{{
.Values.webhook.failurePolicy
}}
name
:
mdynamographdeployment.kb.io
{{
- if .Values.webhook.namespaceSelector
}}
namespaceSelector
:
{{
- toYaml .Values.webhook.namespaceSelector | nindent 4
}}
{{
- else if .Values.namespaceRestriction.enabled
}}
namespaceSelector
:
matchLabels
:
kubernetes.io/metadata.name
:
{{
.Release.Namespace
}}
{{
- end
}}
rules
:
-
apiGroups
:
-
nvidia.com
apiVersions
:
-
v1alpha1
operations
:
-
CREATE
resources
:
-
dynamographdeployments
sideEffects
:
None
timeoutSeconds
:
{{
.Values.webhook.timeoutSeconds
}}
{{
- end
}}
deploy/operator/cmd/main.go
View file @
4f686262
...
...
@@ -54,6 +54,7 @@ import (
lwsscheme
"sigs.k8s.io/lws/client-go/clientset/versioned/scheme"
volcanoscheme
"volcano.sh/apis/pkg/client/clientset/versioned/scheme"
semver
"github.com/Masterminds/semver/v3"
nvidiacomv1alpha1
"github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/controller"
...
...
@@ -66,6 +67,7 @@ import (
"github.com/ai-dynamo/dynamo/deploy/operator/internal/secret"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/secrets"
internalwebhook
"github.com/ai-dynamo/dynamo/deploy/operator/internal/webhook"
webhookdefaulting
"github.com/ai-dynamo/dynamo/deploy/operator/internal/webhook/defaulting"
webhookvalidation
"github.com/ai-dynamo/dynamo/deploy/operator/internal/webhook/validation"
grovev1alpha1
"github.com/ai-dynamo/grove/operator/api/core/v1alpha1"
istioclientsetscheme
"istio.io/client-go/pkg/clientset/versioned/scheme"
...
...
@@ -257,6 +259,14 @@ func main() {
os
.
Exit
(
1
)
}
// Validate and normalize operator version to semver
if
_
,
err
:=
semver
.
NewVersion
(
operatorVersion
);
err
!=
nil
{
setupLog
.
Info
(
"WARNING: operator-version is not valid semver, falling back to 0.0.0-unknown"
,
"provided"
,
operatorVersion
,
"error"
,
err
.
Error
())
operatorVersion
=
"0.0.0-unknown"
}
setupLog
.
Info
(
"Operator version configured"
,
"version"
,
operatorVersion
)
// Validate discoveryBackend value
if
discoveryBackend
!=
"kubernetes"
&&
discoveryBackend
!=
"etcd"
{
setupLog
.
Error
(
nil
,
"invalid discovery-backend value, must be 'kubernetes' or 'etcd'"
,
"value"
,
discoveryBackend
)
...
...
@@ -739,6 +749,17 @@ func main() {
}
setupLog
.
Info
(
"Validation webhooks registered successfully"
)
// Register defaulting (mutating) webhook handlers
setupLog
.
Info
(
"Registering defaulting webhooks"
)
dgdDefaulter
:=
webhookdefaulting
.
NewDGDDefaulter
(
operatorVersion
)
if
err
=
dgdDefaulter
.
RegisterWithManager
(
mgr
);
err
!=
nil
{
setupLog
.
Error
(
err
,
"unable to register webhook"
,
"webhook"
,
"DynamoGraphDeployment-defaulting"
)
os
.
Exit
(
1
)
}
setupLog
.
Info
(
"Defaulting webhooks registered successfully"
)
}
//+kubebuilder:scaffold:builder
...
...
deploy/operator/go.mod
View file @
4f686262
...
...
@@ -4,6 +4,7 @@ go 1.25.0
require (
emperror.dev/errors v0.8.1
github.com/Masterminds/semver/v3 v3.4.0
github.com/ai-dynamo/grove/operator/api v0.1.0-alpha.6
github.com/bsm/gomega v1.27.10
github.com/go-logr/logr v1.4.3
...
...
@@ -30,7 +31,6 @@ require (
)
require (
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
...
...
deploy/operator/internal/consts/consts.go
View file @
4f686262
...
...
@@ -81,6 +81,10 @@ const (
DefaultGroveTerminationDelay
=
15
*
time
.
Minute
// Operator origin version: stamped on DGD at creation time by mutating webhook.
// Records which operator version created the resource, enabling version-gated behavior changes.
KubeAnnotationDynamoOperatorOriginVersion
=
"nvidia.com/dynamo-operator-origin-version"
// Metrics related constants
KubeAnnotationEnableMetrics
=
"nvidia.com/enable-metrics"
// User-provided annotation to control metrics
KubeLabelMetricsEnabled
=
"nvidia.com/metrics-enabled"
// Controller-managed label for pod selection
...
...
deploy/operator/internal/dynamo/graph.go
View file @
4f686262
...
...
@@ -270,7 +270,7 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD
labels
[
commonconsts
.
KubeLabelDynamoNamespace
]
=
dynamoNamespace
labels
[
commonconsts
.
KubeLabelDynamoGraphDeploymentName
]
=
parentDynamoGraphDeployment
.
Name
// Propagate
metrics
annotation from parent deployment if present
// Propagate annotation
s
from parent deployment if present
if
parentDynamoGraphDeployment
.
Annotations
!=
nil
{
if
deployment
.
Spec
.
Annotations
==
nil
{
deployment
.
Spec
.
Annotations
=
make
(
map
[
string
]
string
)
...
...
@@ -281,6 +281,10 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD
if
val
,
exists
:=
parentDynamoGraphDeployment
.
Annotations
[
commonconsts
.
KubeAnnotationDynamoDiscoveryBackend
];
exists
{
deployment
.
Spec
.
Annotations
[
commonconsts
.
KubeAnnotationDynamoDiscoveryBackend
]
=
val
}
// Propagate operator origin version for version-gated behavior in backends
if
val
,
exists
:=
parentDynamoGraphDeployment
.
Annotations
[
commonconsts
.
KubeAnnotationDynamoOperatorOriginVersion
];
exists
{
deployment
.
Spec
.
Annotations
[
commonconsts
.
KubeAnnotationDynamoOperatorOriginVersion
]
=
val
}
}
// Apply restart annotation if this service should be restarted.
...
...
@@ -1211,6 +1215,14 @@ func GenerateGrovePodCliqueSet(
component
.
Annotations
[
commonconsts
.
KubeAnnotationDynamoDiscoveryBackend
]
=
discoveryBackend
}
// Propagate operator origin version for version-gated behavior in backends
if
val
,
exists
:=
dynamoDeployment
.
Annotations
[
commonconsts
.
KubeAnnotationDynamoOperatorOriginVersion
];
exists
{
if
component
.
Annotations
==
nil
{
component
.
Annotations
=
make
(
map
[
string
]
string
)
}
component
.
Annotations
[
commonconsts
.
KubeAnnotationDynamoOperatorOriginVersion
]
=
val
}
// Get checkpoint info for this service if available
var
checkpointInfo
*
checkpoint
.
CheckpointInfo
if
checkpointInfoByService
!=
nil
{
...
...
deploy/operator/internal/webhook/defaulting/dynamographdeployment_handler.go
0 → 100644
View file @
4f686262
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
defaulting
import
(
"context"
"fmt"
nvidiacomv1alpha1
"github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
admissionv1
"k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
const
(
dgdDefaultingWebhookName
=
"dynamographdeployment-defaulting-webhook"
dgdDefaultingWebhookPath
=
"/mutate-nvidia-com-v1alpha1-dynamographdeployment"
)
// DGDDefaulter is a mutating webhook handler that stamps DynamoGraphDeployments
// with the operator version on CREATE. This provides a general-purpose mechanism
// for version-gated behavior changes in the controller.
type
DGDDefaulter
struct
{
OperatorVersion
string
}
// NewDGDDefaulter creates a new DGDDefaulter with the given operator version.
func
NewDGDDefaulter
(
operatorVersion
string
)
*
DGDDefaulter
{
return
&
DGDDefaulter
{
OperatorVersion
:
operatorVersion
,
}
}
// Default implements admission.CustomDefaulter.
// On CREATE: stamps nvidia.com/dynamo-operator-origin-version with the operator version.
// On UPDATE: does nothing -- the origin version is immutable once set.
func
(
d
*
DGDDefaulter
)
Default
(
ctx
context
.
Context
,
obj
runtime
.
Object
)
error
{
logger
:=
log
.
FromContext
(
ctx
)
.
WithName
(
dgdDefaultingWebhookName
)
dgd
,
ok
:=
obj
.
(
*
nvidiacomv1alpha1
.
DynamoGraphDeployment
)
if
!
ok
{
return
fmt
.
Errorf
(
"expected DynamoGraphDeployment but got %T"
,
obj
)
}
req
,
err
:=
admission
.
RequestFromContext
(
ctx
)
if
err
!=
nil
{
logger
.
Error
(
err
,
"failed to get admission request from context, skipping defaulting"
)
return
nil
}
if
req
.
Operation
==
admissionv1
.
Create
{
if
dgd
.
Annotations
==
nil
{
dgd
.
Annotations
=
make
(
map
[
string
]
string
)
}
// Stamp operator version on creation (don't overwrite if already set)
if
_
,
exists
:=
dgd
.
Annotations
[
consts
.
KubeAnnotationDynamoOperatorOriginVersion
];
!
exists
{
dgd
.
Annotations
[
consts
.
KubeAnnotationDynamoOperatorOriginVersion
]
=
d
.
OperatorVersion
logger
.
Info
(
"stamped operator origin version on DGD"
,
"name"
,
dgd
.
Name
,
"namespace"
,
dgd
.
Namespace
,
"version"
,
d
.
OperatorVersion
)
}
}
return
nil
}
// RegisterWithManager registers the defaulting webhook with the manager.
func
(
d
*
DGDDefaulter
)
RegisterWithManager
(
mgr
manager
.
Manager
)
error
{
webhook
:=
admission
.
WithCustomDefaulter
(
mgr
.
GetScheme
(),
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{},
d
)
.
WithRecoverPanic
(
true
)
mgr
.
GetWebhookServer
()
.
Register
(
dgdDefaultingWebhookPath
,
webhook
)
return
nil
}
deploy/operator/internal/webhook/defaulting/dynamographdeployment_handler_test.go
0 → 100644
View file @
4f686262
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
defaulting
import
(
"context"
"testing"
nvidiacomv1alpha1
"github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
admissionv1
"k8s.io/api/admission/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// admissionCtx builds a context carrying an admission request for the given operation.
func
admissionCtx
(
op
admissionv1
.
Operation
)
context
.
Context
{
return
admission
.
NewContextWithRequest
(
context
.
Background
(),
admission
.
Request
{
AdmissionRequest
:
admissionv1
.
AdmissionRequest
{
Operation
:
op
,
},
})
}
func
TestDGDDefaulter_Default
(
t
*
testing
.
T
)
{
const
testVersion
=
"0.8.0"
tests
:=
[]
struct
{
name
string
operatorVersion
string
ctx
context
.
Context
dgd
*
nvidiacomv1alpha1
.
DynamoGraphDeployment
wantAnnotation
string
wantErr
bool
}{
{
name
:
"CREATE stamps operator version on new DGD without annotations"
,
operatorVersion
:
testVersion
,
ctx
:
admissionCtx
(
admissionv1
.
Create
),
dgd
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-dgd"
,
Namespace
:
"default"
,
},
},
wantAnnotation
:
testVersion
,
},
{
name
:
"CREATE stamps operator version on DGD with existing annotations"
,
operatorVersion
:
testVersion
,
ctx
:
admissionCtx
(
admissionv1
.
Create
),
dgd
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-dgd"
,
Namespace
:
"default"
,
Annotations
:
map
[
string
]
string
{
"some-other-annotation"
:
"some-value"
,
},
},
},
wantAnnotation
:
testVersion
,
},
{
name
:
"CREATE does not overwrite pre-existing origin version"
,
operatorVersion
:
testVersion
,
ctx
:
admissionCtx
(
admissionv1
.
Create
),
dgd
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-dgd"
,
Namespace
:
"default"
,
Annotations
:
map
[
string
]
string
{
consts
.
KubeAnnotationDynamoOperatorOriginVersion
:
"0.7.0"
,
},
},
},
wantAnnotation
:
"0.7.0"
,
},
{
name
:
"UPDATE does not stamp annotation"
,
operatorVersion
:
testVersion
,
ctx
:
admissionCtx
(
admissionv1
.
Update
),
dgd
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-dgd"
,
Namespace
:
"default"
,
},
},
wantAnnotation
:
""
,
},
{
name
:
"UPDATE preserves existing annotation"
,
operatorVersion
:
testVersion
,
ctx
:
admissionCtx
(
admissionv1
.
Update
),
dgd
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-dgd"
,
Namespace
:
"default"
,
Annotations
:
map
[
string
]
string
{
consts
.
KubeAnnotationDynamoOperatorOriginVersion
:
"0.7.0"
,
},
},
},
wantAnnotation
:
"0.7.0"
,
},
{
name
:
"DELETE does not stamp annotation"
,
operatorVersion
:
testVersion
,
ctx
:
admissionCtx
(
admissionv1
.
Delete
),
dgd
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-dgd"
,
Namespace
:
"default"
,
},
},
wantAnnotation
:
""
,
},
{
name
:
"no admission request in context skips defaulting gracefully"
,
operatorVersion
:
testVersion
,
ctx
:
context
.
Background
(),
dgd
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-dgd"
,
Namespace
:
"default"
,
},
},
wantAnnotation
:
""
,
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
defaulter
:=
NewDGDDefaulter
(
tt
.
operatorVersion
)
err
:=
defaulter
.
Default
(
tt
.
ctx
,
tt
.
dgd
)
if
(
err
!=
nil
)
!=
tt
.
wantErr
{
t
.
Errorf
(
"Default() error = %v, wantErr %v"
,
err
,
tt
.
wantErr
)
return
}
got
:=
""
if
tt
.
dgd
.
Annotations
!=
nil
{
got
=
tt
.
dgd
.
Annotations
[
consts
.
KubeAnnotationDynamoOperatorOriginVersion
]
}
if
got
!=
tt
.
wantAnnotation
{
t
.
Errorf
(
"annotation %q = %q, want %q"
,
consts
.
KubeAnnotationDynamoOperatorOriginVersion
,
got
,
tt
.
wantAnnotation
)
}
})
}
}
deploy/operator/internal/webhook/validation/dynamographdeployment.go
View file @
4f686262
...
...
@@ -23,7 +23,9 @@ import (
"fmt"
"sort"
semver
"github.com/Masterminds/semver/v3"
nvidiacomv1alpha1
"github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
internalwebhook
"github.com/ai-dynamo/dynamo/deploy/operator/internal/webhook"
authenticationv1
"k8s.io/api/authentication/v1"
ctrl
"sigs.k8s.io/controller-runtime"
...
...
@@ -62,6 +64,11 @@ func (v *DynamoGraphDeploymentValidator) Validate(ctx context.Context) (admissio
return
nil
,
fmt
.
Errorf
(
"spec.services must have at least one service"
)
}
// Validate annotations
if
err
:=
v
.
validateAnnotations
();
err
!=
nil
{
return
nil
,
err
}
// Validate PVCs
if
err
:=
v
.
validatePVCs
();
err
!=
nil
{
return
nil
,
err
...
...
@@ -335,6 +342,26 @@ func (v *DynamoGraphDeploymentValidator) validateRestartStrategyOrder() error {
return
err
}
// validateAnnotations validates known DGD annotations have valid values.
func
(
v
*
DynamoGraphDeploymentValidator
)
validateAnnotations
()
error
{
annotations
:=
v
.
deployment
.
GetAnnotations
()
if
annotations
==
nil
{
return
nil
}
var
errs
[]
error
// Validate operator origin version is valid semver (if present)
if
value
,
exists
:=
annotations
[
consts
.
KubeAnnotationDynamoOperatorOriginVersion
];
exists
{
if
_
,
err
:=
semver
.
NewVersion
(
value
);
err
!=
nil
{
errs
=
append
(
errs
,
fmt
.
Errorf
(
"annotation %s has invalid value %q: must be valid semver"
,
consts
.
KubeAnnotationDynamoOperatorOriginVersion
,
value
))
}
}
return
errors
.
Join
(
errs
...
)
}
func
getUnique
[
T
comparable
](
slice
[]
T
)
[]
T
{
seen
:=
make
(
map
[
T
]
struct
{},
len
(
slice
))
uniqueSlice
:=
make
([]
T
,
0
,
len
(
slice
))
...
...
deploy/operator/internal/webhook/validation/dynamographdeployment_test.go
View file @
4f686262
...
...
@@ -24,6 +24,7 @@ import (
"testing"
nvidiacomv1alpha1
"github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
corev1
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
...
...
@@ -507,6 +508,95 @@ func TestDynamoGraphDeploymentValidator_Validate(t *testing.T) {
},
wantErr
:
false
,
},
// Annotation validation test cases
{
name
:
"no annotations is valid"
,
deployment
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-graph"
,
Namespace
:
"default"
,
},
Spec
:
nvidiacomv1alpha1
.
DynamoGraphDeploymentSpec
{
Services
:
map
[
string
]
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
"main"
:
{},
},
},
},
wantErr
:
false
,
},
{
name
:
"valid annotation dynamo-operator-origin-version with semver"
,
deployment
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-graph"
,
Namespace
:
"default"
,
Annotations
:
map
[
string
]
string
{
consts
.
KubeAnnotationDynamoOperatorOriginVersion
:
"1.0.0"
,
},
},
Spec
:
nvidiacomv1alpha1
.
DynamoGraphDeploymentSpec
{
Services
:
map
[
string
]
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
"main"
:
{},
},
},
},
wantErr
:
false
,
},
{
name
:
"valid annotation dynamo-operator-origin-version with pre-release"
,
deployment
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-graph"
,
Namespace
:
"default"
,
Annotations
:
map
[
string
]
string
{
consts
.
KubeAnnotationDynamoOperatorOriginVersion
:
"1.0.0-dev"
,
},
},
Spec
:
nvidiacomv1alpha1
.
DynamoGraphDeploymentSpec
{
Services
:
map
[
string
]
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
"main"
:
{},
},
},
},
wantErr
:
false
,
},
{
name
:
"valid annotation dynamo-operator-origin-version fallback version"
,
deployment
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-graph"
,
Namespace
:
"default"
,
Annotations
:
map
[
string
]
string
{
consts
.
KubeAnnotationDynamoOperatorOriginVersion
:
"0.0.0-unknown"
,
},
},
Spec
:
nvidiacomv1alpha1
.
DynamoGraphDeploymentSpec
{
Services
:
map
[
string
]
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
"main"
:
{},
},
},
},
wantErr
:
false
,
},
{
name
:
"invalid annotation dynamo-operator-origin-version not semver"
,
deployment
:
&
nvidiacomv1alpha1
.
DynamoGraphDeployment
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-graph"
,
Namespace
:
"default"
,
Annotations
:
map
[
string
]
string
{
consts
.
KubeAnnotationDynamoOperatorOriginVersion
:
"not-a-version"
,
},
},
Spec
:
nvidiacomv1alpha1
.
DynamoGraphDeploymentSpec
{
Services
:
map
[
string
]
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
"main"
:
{},
},
},
},
wantErr
:
true
,
errMsg
:
`annotation nvidia.com/dynamo-operator-origin-version has invalid value "not-a-version": must be valid semver`
,
},
}
for
_
,
tt
:=
range
tests
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment