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
c8770464
Unverified
Commit
c8770464
authored
Jan 12, 2026
by
hhzhang16
Committed by
GitHub
Jan 12, 2026
Browse files
feat: normalize dynamo namespace computation (#5231)
Signed-off-by:
Hannah Zhang
<
hannahz@nvidia.com
>
parent
abd4b5d9
Changes
69
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
302 additions
and
106 deletions
+302
-106
benchmarks/profiler/utils/config.py
benchmarks/profiler/utils/config.py
+0
-1
benchmarks/profiler/utils/dgd_generation.py
benchmarks/profiler/utils/dgd_generation.py
+0
-14
deploy/discovery/dgd.yaml
deploy/discovery/dgd.yaml
+0
-3
deploy/operator/api/v1alpha1/dynamocomponentdeployment_types.go
.../operator/api/v1alpha1/dynamocomponentdeployment_types.go
+17
-0
deploy/operator/api/v1alpha1/dynamographdeployment_types.go
deploy/operator/api/v1alpha1/dynamographdeployment_types.go
+5
-0
deploy/operator/internal/controller/dynamocomponentdeployment_controller_test.go
...l/controller/dynamocomponentdeployment_controller_test.go
+2
-2
deploy/operator/internal/controller/dynamographdeployment_controller.go
...r/internal/controller/dynamographdeployment_controller.go
+11
-9
deploy/operator/internal/dynamo/graph.go
deploy/operator/internal/dynamo/graph.go
+4
-7
deploy/operator/internal/dynamo/graph_test.go
deploy/operator/internal/dynamo/graph_test.go
+118
-1
deploy/operator/internal/webhook/validation/dynamocomponentdeployment.go
.../internal/webhook/validation/dynamocomponentdeployment.go
+2
-1
deploy/operator/internal/webhook/validation/dynamographdeployment.go
...ator/internal/webhook/validation/dynamographdeployment.go
+2
-1
deploy/operator/internal/webhook/validation/shared.go
deploy/operator/internal/webhook/validation/shared.go
+21
-8
deploy/operator/internal/webhook/validation/shared_test.go
deploy/operator/internal/webhook/validation/shared_test.go
+120
-42
examples/backends/mocker/deploy/agg.yaml
examples/backends/mocker/deploy/agg.yaml
+0
-2
examples/backends/mocker/deploy/disagg.yaml
examples/backends/mocker/deploy/disagg.yaml
+0
-3
examples/backends/sglang/deploy/agg.yaml
examples/backends/sglang/deploy/agg.yaml
+0
-2
examples/backends/sglang/deploy/agg_logging.yaml
examples/backends/sglang/deploy/agg_logging.yaml
+0
-2
examples/backends/sglang/deploy/agg_router.yaml
examples/backends/sglang/deploy/agg_router.yaml
+0
-2
examples/backends/sglang/deploy/disagg-multinode.yaml
examples/backends/sglang/deploy/disagg-multinode.yaml
+0
-3
examples/backends/sglang/deploy/disagg.yaml
examples/backends/sglang/deploy/disagg.yaml
+0
-3
No files found.
benchmarks/profiler/utils/config.py
View file @
c8770464
...
...
@@ -93,7 +93,6 @@ class DgdPlannerServiceConfig(BaseModel):
automatically created and mounted by the profiler; no PVC dependencies
"""
dynamoNamespace
:
str
=
"dynamo"
# placeholder
componentType
:
str
=
"planner"
replicas
:
int
=
1
extraPodSpec
:
PodSpec
=
PodSpec
(
...
...
benchmarks/profiler/utils/dgd_generation.py
View file @
c8770464
...
...
@@ -261,7 +261,6 @@ def generate_dgd_config_with_planner(
# add the planner service
planner_config
=
DgdPlannerServiceConfig
()
frontend_service
=
config
.
spec
.
services
[
"Frontend"
]
planner_config
.
dynamoNamespace
=
getattr
(
frontend_service
,
"dynamoNamespace"
,
"dynamo"
)
# type: ignore[attr-defined]
frontend_image
:
Optional
[
str
]
=
None
if
frontend_service
.
extraPodSpec
and
frontend_service
.
extraPodSpec
.
mainContainer
:
frontend_image
=
frontend_service
.
extraPodSpec
.
mainContainer
.
image
...
...
@@ -275,8 +274,6 @@ def generate_dgd_config_with_planner(
# Override profiling-specific arguments with results from profiling
# Remove and re-add to ensure correct values from profiling context
# Note: --namespace is NOT added here; planner gets it from DYN_NAMESPACE env var
# which is automatically injected by the operator based on dynamoNamespace
planner_args
=
[
arg
for
arg
in
planner_args
...
...
@@ -511,17 +508,6 @@ def _generate_mocker_config_with_planner(
# Add planner service (reuse the same planner config but with mocker backend)
mocker_planner_dict
=
copy
.
deepcopy
(
planner_dict
)
# Get the mocker's dynamoNamespace from Frontend service
mocker_namespace
=
(
mocker_config
.
get
(
"spec"
,
{})
.
get
(
"services"
,
{})
.
get
(
"Frontend"
,
{})
.
get
(
"dynamoNamespace"
,
"mocker-disagg"
)
)
# Update planner's dynamoNamespace to match mocker's namespace
mocker_planner_dict
[
"dynamoNamespace"
]
=
mocker_namespace
# Planner args use --key=value format, so we need to find and replace
planner_main_container
=
mocker_planner_dict
.
get
(
"extraPodSpec"
,
{}).
get
(
"mainContainer"
,
{}
...
...
deploy/discovery/dgd.yaml
View file @
c8770464
...
...
@@ -14,14 +14,12 @@ spec:
value
:
"
debug"
services
:
Frontend
:
dynamoNamespace
:
dynamo
componentType
:
frontend
replicas
:
1
extraPodSpec
:
mainContainer
:
image
:
${IMAGE}
VllmDecodeWorker
:
dynamoNamespace
:
vllm-disagg
componentType
:
decode
replicas
:
1
resources
:
...
...
@@ -40,7 +38,6 @@ spec:
-
--model
-
Qwen/Qwen3-0.6B
VllmPrefillWorker
:
dynamoNamespace
:
vllm-disagg
componentType
:
prefill
replicas
:
1
resources
:
...
...
deploy/operator/api/v1alpha1/dynamocomponentdeployment_types.go
View file @
c8770464
...
...
@@ -20,6 +20,8 @@
package
v1alpha1
import
(
"fmt"
commonconsts
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
corev1
"k8s.io/api/core/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
...
...
@@ -309,6 +311,21 @@ func (s *DynamoComponentDeployment) GetParentGraphDeploymentNamespace() string {
return
s
.
GetNamespace
()
}
// GetDynamoNamespace returns the Dynamo namespace for this component.
func
(
s
*
DynamoComponentDeployment
)
GetDynamoNamespace
()
string
{
return
ComputeDynamoNamespace
(
s
.
Spec
.
GlobalDynamoNamespace
,
s
.
GetNamespace
(),
s
.
GetParentGraphDeploymentName
())
}
// ComputeDynamoNamespace is the single source of truth for computing the Dynamo namespace.
// If globalDynamoNamespace is true, returns "dynamo" (global constant).
// Otherwise, returns {k8sNamespace}-{dgdName}.
func
ComputeDynamoNamespace
(
globalDynamoNamespace
bool
,
k8sNamespace
,
dgdName
string
)
string
{
if
globalDynamoNamespace
{
return
commonconsts
.
GlobalDynamoNamespace
}
return
fmt
.
Sprintf
(
"%s-%s"
,
k8sNamespace
,
dgdName
)
}
// ModelReference identifies a model served by this component
type
ModelReference
struct
{
// Name is the base model identifier (e.g., "llama-3-70b-instruct-v1")
...
...
deploy/operator/api/v1alpha1/dynamographdeployment_types.go
View file @
c8770464
...
...
@@ -233,3 +233,8 @@ func (s *DynamoGraphDeployment) HasAnyMultinodeService() bool {
}
return
false
}
// GetDynamoNamespaceForService returns the Dynamo namespace for a given service.
func
(
s
*
DynamoGraphDeployment
)
GetDynamoNamespaceForService
(
service
*
DynamoComponentDeploymentSharedSpec
)
string
{
return
ComputeDynamoNamespace
(
service
.
GlobalDynamoNamespace
,
s
.
GetNamespace
(),
s
.
GetName
())
}
deploy/operator/internal/controller/dynamocomponentdeployment_controller_test.go
View file @
c8770464
...
...
@@ -851,7 +851,7 @@ func TestDynamoComponentDeploymentReconciler_generateLeaderWorkerSet(t *testing.
{
Name
:
commonconsts
.
DynamoComponentEnvVar
,
Value
:
commonconsts
.
ComponentTypeWorker
},
{
Name
:
commonconsts
.
DynamoDiscoveryBackendEnvVar
,
Value
:
"kubernetes"
},
{
Name
:
"DYN_HEALTH_CHECK_ENABLED"
,
Value
:
"false"
},
{
Name
:
commonconsts
.
DynamoNamespaceEnvVar
,
Value
:
"default"
},
{
Name
:
commonconsts
.
DynamoNamespaceEnvVar
,
Value
:
"default
-test-lws-deploy
"
},
{
Name
:
"DYN_PARENT_DGD_K8S_NAME"
,
Value
:
"test-lws-deploy"
},
{
Name
:
"DYN_PARENT_DGD_K8S_NAMESPACE"
,
Value
:
"default"
},
{
Name
:
"DYN_SYSTEM_ENABLED"
,
Value
:
"true"
},
...
...
@@ -986,7 +986,7 @@ func TestDynamoComponentDeploymentReconciler_generateLeaderWorkerSet(t *testing.
{
Name
:
commonconsts
.
DynamoComponentEnvVar
,
Value
:
commonconsts
.
ComponentTypeWorker
},
{
Name
:
commonconsts
.
DynamoDiscoveryBackendEnvVar
,
Value
:
"kubernetes"
},
{
Name
:
"DYN_HEALTH_CHECK_ENABLED"
,
Value
:
"false"
},
{
Name
:
commonconsts
.
DynamoNamespaceEnvVar
,
Value
:
"default"
},
{
Name
:
commonconsts
.
DynamoNamespaceEnvVar
,
Value
:
"default
-test-lws-deploy
"
},
{
Name
:
"DYN_PARENT_DGD_K8S_NAME"
,
Value
:
"test-lws-deploy"
},
{
Name
:
"DYN_PARENT_DGD_K8S_NAMESPACE"
,
Value
:
"default"
},
{
Name
:
"DYN_SYSTEM_ENABLED"
,
Value
:
"true"
},
...
...
deploy/operator/internal/controller/dynamographdeployment_controller.go
View file @
c8770464
...
...
@@ -154,6 +154,17 @@ func (r *DynamoGraphDeploymentReconciler) Reconcile(ctx context.Context, req ctr
logger
.
Info
(
"Reconciliation done"
)
}()
// Handle finalizer
deleted
,
err
:=
commoncontroller
.
HandleFinalizer
(
ctx
,
dynamoDeployment
,
r
.
Client
,
r
)
if
err
!=
nil
{
logger
.
Error
(
err
,
"failed to handle the finalizer"
)
reason
=
"failed_to_handle_the_finalizer"
return
ctrl
.
Result
{},
err
}
if
deleted
{
return
ctrl
.
Result
{},
nil
}
// Validate the DynamoGraphDeployment spec (defense in depth - only when webhooks are disabled)
if
!
r
.
Config
.
WebhooksEnabled
{
validator
:=
webhookvalidation
.
NewDynamoGraphDeploymentValidator
(
dynamoDeployment
)
...
...
@@ -176,15 +187,6 @@ func (r *DynamoGraphDeploymentReconciler) Reconcile(ctx context.Context, req ctr
}
}
deleted
,
err
:=
commoncontroller
.
HandleFinalizer
(
ctx
,
dynamoDeployment
,
r
.
Client
,
r
)
if
err
!=
nil
{
logger
.
Error
(
err
,
"failed to handle the finalizer"
)
reason
=
"failed_to_handle_the_finalizer"
return
ctrl
.
Result
{},
err
}
if
deleted
{
return
ctrl
.
Result
{},
nil
}
reconcileResult
,
err
:=
r
.
reconcileResources
(
ctx
,
dynamoDeployment
)
state
=
reconcileResult
.
State
...
...
deploy/operator/internal/dynamo/graph.go
View file @
c8770464
...
...
@@ -337,10 +337,7 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD
}
func
getDynamoNamespace
(
object
metav1
.
Object
,
service
*
v1alpha1
.
DynamoComponentDeploymentSharedSpec
)
string
{
if
service
.
GlobalDynamoNamespace
{
return
commonconsts
.
GlobalDynamoNamespace
}
return
fmt
.
Sprintf
(
"%s-%s"
,
object
.
GetNamespace
(),
object
.
GetName
())
return
v1alpha1
.
ComputeDynamoNamespace
(
service
.
GlobalDynamoNamespace
,
object
.
GetNamespace
(),
object
.
GetName
())
}
// updateDynDeploymentConfig updates the runtime config object for the given dynamoDeploymentComponent
...
...
@@ -1071,15 +1068,15 @@ func setMetricsLabels(labels map[string]string, dynamoGraphDeployment *v1alpha1.
}
func
generateComponentContext
(
component
*
v1alpha1
.
DynamoComponentDeploymentSharedSpec
,
parentGraphDeploymentName
string
,
namespace
string
,
numberOfNodes
int32
,
discoveryBackend
string
)
ComponentContext
{
dynamoNamespace
:=
v1alpha1
.
ComputeDynamoNamespace
(
component
.
GlobalDynamoNamespace
,
namespace
,
parentGraphDeploymentName
)
componentContext
:=
ComponentContext
{
numberOfNodes
:
numberOfNodes
,
ComponentType
:
component
.
ComponentType
,
ParentGraphDeploymentName
:
parentGraphDeploymentName
,
ParentGraphDeploymentNamespace
:
namespace
,
DiscoveryBackend
:
discoveryBackend
,
}
if
component
.
DynamoNamespace
!=
nil
{
componentContext
.
DynamoNamespace
=
*
component
.
DynamoNamespace
DynamoNamespace
:
dynamoNamespace
,
}
return
componentContext
}
...
...
deploy/operator/internal/dynamo/graph_test.go
View file @
c8770464
...
...
@@ -780,6 +780,123 @@ func Test_GetDynamoComponentDeploymentsGlobalNamespace(t *testing.T) {
}
}
// TestGenerateComponentContext tests the generateComponentContext function
// to ensure it correctly computes the DynamoNamespace from authoritative sources
// (k8s namespace + DGD name), ignoring any deprecated dynamoNamespace field.
func
TestGenerateComponentContext
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
component
*
v1alpha1
.
DynamoComponentDeploymentSharedSpec
parentGraphDeploymentName
string
namespace
string
numberOfNodes
int32
discoveryBackend
string
expectedDynamoNamespace
string
expectedComponentType
string
expectedParentDGDName
string
expectedParentDGDNamespace
string
}{
{
name
:
"namespace-scoped operator: computes correct dynamo namespace"
,
component
:
&
v1alpha1
.
DynamoComponentDeploymentSharedSpec
{
ComponentType
:
commonconsts
.
ComponentTypePlanner
,
// Deprecated field set to incorrect value - should be ignored
DynamoNamespace
:
ptr
.
To
(
"old-incorrect-value"
),
},
parentGraphDeploymentName
:
"my-deployment"
,
namespace
:
"my-namespace"
,
numberOfNodes
:
1
,
discoveryBackend
:
"kubernetes"
,
expectedDynamoNamespace
:
"my-namespace-my-deployment"
,
expectedComponentType
:
commonconsts
.
ComponentTypePlanner
,
expectedParentDGDName
:
"my-deployment"
,
expectedParentDGDNamespace
:
"my-namespace"
,
},
{
name
:
"deprecated dynamoNamespace field is ignored"
,
component
:
&
v1alpha1
.
DynamoComponentDeploymentSharedSpec
{
ComponentType
:
commonconsts
.
ComponentTypeFrontend
,
// This is the bug case: profiler sets dynamoNamespace to just DGD name
DynamoNamespace
:
ptr
.
To
(
"vllm-disagg"
),
},
parentGraphDeploymentName
:
"vllm-disagg"
,
namespace
:
"djangoz"
,
numberOfNodes
:
1
,
discoveryBackend
:
"kubernetes"
,
expectedDynamoNamespace
:
"djangoz-vllm-disagg"
,
// Should be k8s-namespace + DGD name
expectedComponentType
:
commonconsts
.
ComponentTypeFrontend
,
expectedParentDGDName
:
"vllm-disagg"
,
expectedParentDGDNamespace
:
"djangoz"
,
},
{
name
:
"GlobalDynamoNamespace takes precedence"
,
component
:
&
v1alpha1
.
DynamoComponentDeploymentSharedSpec
{
ComponentType
:
commonconsts
.
ComponentTypeWorker
,
GlobalDynamoNamespace
:
true
,
// Even with deprecated field set, GlobalDynamoNamespace should win
DynamoNamespace
:
ptr
.
To
(
"should-be-ignored"
),
},
parentGraphDeploymentName
:
"shared-frontend"
,
namespace
:
"production"
,
numberOfNodes
:
2
,
discoveryBackend
:
"etcd"
,
expectedDynamoNamespace
:
commonconsts
.
GlobalDynamoNamespace
,
// "dynamo"
expectedComponentType
:
commonconsts
.
ComponentTypeWorker
,
expectedParentDGDName
:
"shared-frontend"
,
expectedParentDGDNamespace
:
"production"
,
},
{
name
:
"nil dynamoNamespace field still computes correctly"
,
component
:
&
v1alpha1
.
DynamoComponentDeploymentSharedSpec
{
ComponentType
:
commonconsts
.
ComponentTypePlanner
,
DynamoNamespace
:
nil
,
// Not set at all
},
parentGraphDeploymentName
:
"test-dgd"
,
namespace
:
"default"
,
numberOfNodes
:
1
,
discoveryBackend
:
"kubernetes"
,
expectedDynamoNamespace
:
"default-test-dgd"
,
expectedComponentType
:
commonconsts
.
ComponentTypePlanner
,
expectedParentDGDName
:
"test-dgd"
,
expectedParentDGDNamespace
:
"default"
,
},
{
name
:
"different namespace and DGD name combinations"
,
component
:
&
v1alpha1
.
DynamoComponentDeploymentSharedSpec
{
ComponentType
:
commonconsts
.
ComponentTypeFrontend
,
},
parentGraphDeploymentName
:
"llama-70b-prod"
,
namespace
:
"ml-inference"
,
numberOfNodes
:
4
,
discoveryBackend
:
"nats"
,
expectedDynamoNamespace
:
"ml-inference-llama-70b-prod"
,
expectedComponentType
:
commonconsts
.
ComponentTypeFrontend
,
expectedParentDGDName
:
"llama-70b-prod"
,
expectedParentDGDNamespace
:
"ml-inference"
,
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
ctx
:=
generateComponentContext
(
tt
.
component
,
tt
.
parentGraphDeploymentName
,
tt
.
namespace
,
tt
.
numberOfNodes
,
tt
.
discoveryBackend
,
)
assert
.
Equal
(
t
,
tt
.
expectedDynamoNamespace
,
ctx
.
DynamoNamespace
,
"DynamoNamespace should be computed from k8s namespace + DGD name"
)
assert
.
Equal
(
t
,
tt
.
expectedComponentType
,
ctx
.
ComponentType
)
assert
.
Equal
(
t
,
tt
.
expectedParentDGDName
,
ctx
.
ParentGraphDeploymentName
)
assert
.
Equal
(
t
,
tt
.
expectedParentDGDNamespace
,
ctx
.
ParentGraphDeploymentNamespace
)
assert
.
Equal
(
t
,
tt
.
numberOfNodes
,
ctx
.
numberOfNodes
)
assert
.
Equal
(
t
,
tt
.
discoveryBackend
,
ctx
.
DiscoveryBackend
)
})
}
}
func
Test_updateDynDeploymentConfig
(
t
*
testing
.
T
)
{
type
args
struct
{
dynamoDeploymentComponent
*
v1alpha1
.
DynamoComponentDeployment
...
...
@@ -5139,7 +5256,7 @@ func TestGenerateBasePodSpec_Worker(t *testing.T) {
{
Name
:
commonconsts
.
DynamoComponentEnvVar
,
Value
:
"worker"
},
{
Name
:
commonconsts
.
DynamoDiscoveryBackendEnvVar
,
Value
:
"kubernetes"
},
{
Name
:
"DYN_HEALTH_CHECK_ENABLED"
,
Value
:
"false"
},
{
Name
:
commonconsts
.
DynamoNamespaceEnvVar
,
Value
:
""
},
{
Name
:
commonconsts
.
DynamoNamespaceEnvVar
,
Value
:
"
default-test-deployment
"
},
{
Name
:
"DYN_PARENT_DGD_K8S_NAME"
,
Value
:
"test-deployment"
},
{
Name
:
"DYN_PARENT_DGD_K8S_NAMESPACE"
,
Value
:
"default"
},
{
Name
:
"DYN_SYSTEM_ENABLED"
,
Value
:
"true"
},
...
...
deploy/operator/internal/webhook/validation/dynamocomponentdeployment.go
View file @
c8770464
...
...
@@ -41,7 +41,8 @@ func NewDynamoComponentDeploymentValidator(deployment *nvidiacomv1alpha1.DynamoC
// Returns warnings and error.
func
(
v
*
DynamoComponentDeploymentValidator
)
Validate
()
(
admission
.
Warnings
,
error
)
{
// Validate shared spec fields using SharedSpecValidator
sharedValidator
:=
NewSharedSpecValidator
(
&
v
.
deployment
.
Spec
.
DynamoComponentDeploymentSharedSpec
,
"spec"
)
calculatedNamespace
:=
v
.
deployment
.
GetDynamoNamespace
()
sharedValidator
:=
NewSharedSpecValidator
(
&
v
.
deployment
.
Spec
.
DynamoComponentDeploymentSharedSpec
,
"spec"
,
calculatedNamespace
)
// DCD-specific validation would go here (currently none)
...
...
deploy/operator/internal/webhook/validation/dynamographdeployment.go
View file @
c8770464
...
...
@@ -226,7 +226,8 @@ func (v *DynamoGraphDeploymentValidator) validateReplicasChanges(old *nvidiacomv
func
(
v
*
DynamoGraphDeploymentValidator
)
validateService
(
serviceName
string
,
service
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
)
(
admission
.
Warnings
,
error
)
{
// Use SharedSpecValidator to validate service spec (which is a DynamoComponentDeploymentSharedSpec)
fieldPath
:=
fmt
.
Sprintf
(
"spec.services[%s]"
,
serviceName
)
sharedValidator
:=
NewSharedSpecValidator
(
service
,
fieldPath
)
calculatedNamespace
:=
v
.
deployment
.
GetDynamoNamespaceForService
(
service
)
sharedValidator
:=
NewSharedSpecValidator
(
service
,
fieldPath
,
calculatedNamespace
)
return
sharedValidator
.
Validate
()
}
...
...
deploy/operator/internal/webhook/validation/shared.go
View file @
c8770464
...
...
@@ -30,20 +30,36 @@ import (
type
SharedSpecValidator
struct
{
spec
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
fieldPath
string
// e.g., "spec" for DCD, "spec.services[foo]" for DGD
calculatedNamespace
string
// The namespace that will be used: {k8s_namespace}-{dgd_name}
}
// NewSharedSpecValidator creates a new validator for DynamoComponentDeploymentSharedSpec.
// fieldPath is used to provide context in error messages (e.g., "spec" or "spec.services[main]").
func
NewSharedSpecValidator
(
spec
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
,
fieldPath
string
)
*
SharedSpecValidator
{
// calculatedNamespace is the namespace the operator will use:
// - If GlobalDynamoNamespace is true: "dynamo" (global constant)
// - Otherwise: {k8s_namespace}-{dgd_name}
func
NewSharedSpecValidator
(
spec
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
,
fieldPath
string
,
calculatedNamespace
string
)
*
SharedSpecValidator
{
return
&
SharedSpecValidator
{
spec
:
spec
,
fieldPath
:
fieldPath
,
calculatedNamespace
:
calculatedNamespace
,
}
}
// Validate performs validation on the shared spec fields.
// Returns warnings (e.g., deprecation notices) and error if validation fails.
func
(
v
*
SharedSpecValidator
)
Validate
()
(
admission
.
Warnings
,
error
)
{
// Collect warnings (e.g., deprecation notices)
var
warnings
admission
.
Warnings
// Warn about deprecated dynamoNamespace field
if
v
.
spec
.
DynamoNamespace
!=
nil
&&
*
v
.
spec
.
DynamoNamespace
!=
""
{
warnings
=
append
(
warnings
,
fmt
.
Sprintf
(
"%s.dynamoNamespace is deprecated and ignored. Value '%s' will be replaced with '%s'. "
+
"Remove this field from your configuration"
,
v
.
fieldPath
,
*
v
.
spec
.
DynamoNamespace
,
v
.
calculatedNamespace
))
}
// Validate replicas if specified
if
v
.
spec
.
Replicas
!=
nil
&&
*
v
.
spec
.
Replicas
<
0
{
return
nil
,
fmt
.
Errorf
(
"%s.replicas must be non-negative"
,
v
.
fieldPath
)
...
...
@@ -68,9 +84,6 @@ func (v *SharedSpecValidator) Validate() (admission.Warnings, error) {
}
}
// Collect warnings (e.g., deprecation notices)
var
warnings
admission
.
Warnings
// Check for deprecated autoscaling field
//nolint:staticcheck // SA1019: Intentionally checking deprecated field to warn users
if
v
.
spec
.
Autoscaling
!=
nil
{
...
...
deploy/operator/internal/webhook/validation/shared_test.go
View file @
c8770464
...
...
@@ -24,6 +24,11 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
)
// ptr is a helper function to create a pointer to a string
func
ptr
(
s
string
)
*
string
{
return
&
s
}
func
TestSharedSpecValidator_Validate
(
t
*
testing
.
T
)
{
var
(
negativeReplicas
=
int32
(
-
1
)
...
...
@@ -34,6 +39,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
name
string
spec
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
fieldPath
string
calculatedNamespace
string
wantErr
bool
errMsg
string
}{
...
...
@@ -61,6 +67,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
false
,
},
{
...
...
@@ -69,9 +76,28 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
Replicas
:
&
negativeReplicas
,
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
true
,
errMsg
:
"spec.replicas must be non-negative"
,
},
{
name
:
"nil dynamoNamespace is allowed"
,
spec
:
&
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
DynamoNamespace
:
nil
,
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
false
,
},
{
name
:
"empty string dynamoNamespace is allowed"
,
spec
:
&
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
DynamoNamespace
:
ptr
(
""
),
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
false
,
},
{
name
:
"ingress enabled without host"
,
spec
:
&
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
...
...
@@ -81,6 +107,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
true
,
errMsg
:
"spec.ingress.host is required when ingress is enabled"
,
},
...
...
@@ -93,6 +120,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
false
,
},
{
...
...
@@ -107,6 +135,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
true
,
errMsg
:
"spec.volumeMounts[0].mountPoint is required when useAsCompilationCache is false"
,
},
...
...
@@ -121,6 +150,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
false
,
},
{
...
...
@@ -134,6 +164,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
false
,
},
{
...
...
@@ -145,6 +176,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
true
,
errMsg
:
"spec.sharedMemory.size is required when disabled is false"
,
},
...
...
@@ -157,6 +189,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
false
,
},
{
...
...
@@ -168,6 +201,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
false
,
},
{
...
...
@@ -176,6 +210,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
Replicas
:
&
negativeReplicas
,
},
fieldPath
:
"spec.services[main]"
,
calculatedNamespace
:
"default-my-dgd"
,
wantErr
:
true
,
errMsg
:
"spec.services[main].replicas must be non-negative"
,
},
...
...
@@ -183,7 +218,7 @@ func TestSharedSpecValidator_Validate(t *testing.T) {
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
validator
:=
NewSharedSpecValidator
(
tt
.
spec
,
tt
.
fieldPath
)
validator
:=
NewSharedSpecValidator
(
tt
.
spec
,
tt
.
fieldPath
,
tt
.
calculatedNamespace
)
_
,
err
:=
validator
.
Validate
()
if
(
err
!=
nil
)
!=
tt
.
wantErr
{
...
...
@@ -205,7 +240,9 @@ func TestSharedSpecValidator_Validate_Warnings(t *testing.T) {
name
string
spec
*
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
fieldPath
string
calculatedNamespace
string
wantWarnings
int
wantWarningContains
string
// optional substring to check in warning
}{
{
name
:
"no warnings for spec without autoscaling"
,
...
...
@@ -213,6 +250,7 @@ func TestSharedSpecValidator_Validate_Warnings(t *testing.T) {
Replicas
:
&
validReplicas
,
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantWarnings
:
0
,
},
{
...
...
@@ -227,13 +265,25 @@ func TestSharedSpecValidator_Validate_Warnings(t *testing.T) {
},
},
fieldPath
:
"spec"
,
calculatedNamespace
:
"default-my-dgd"
,
wantWarnings
:
1
,
},
{
name
:
"warning for deprecated dynamoNamespace field shows calculated namespace"
,
spec
:
&
nvidiacomv1alpha1
.
DynamoComponentDeploymentSharedSpec
{
Replicas
:
&
validReplicas
,
DynamoNamespace
:
ptr
(
"my-custom-namespace"
),
},
fieldPath
:
"spec.services[Frontend]"
,
calculatedNamespace
:
"hannahz-trtllm-disagg"
,
wantWarnings
:
1
,
wantWarningContains
:
"Value 'my-custom-namespace' will be replaced with 'hannahz-trtllm-disagg'"
,
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
validator
:=
NewSharedSpecValidator
(
tt
.
spec
,
tt
.
fieldPath
)
validator
:=
NewSharedSpecValidator
(
tt
.
spec
,
tt
.
fieldPath
,
tt
.
calculatedNamespace
)
warnings
,
err
:=
validator
.
Validate
()
if
err
!=
nil
{
...
...
@@ -244,6 +294,34 @@ func TestSharedSpecValidator_Validate_Warnings(t *testing.T) {
if
len
(
warnings
)
!=
tt
.
wantWarnings
{
t
.
Errorf
(
"SharedSpecValidator.Validate() warnings count = %d, want %d"
,
len
(
warnings
),
tt
.
wantWarnings
)
}
if
tt
.
wantWarningContains
!=
""
&&
len
(
warnings
)
>
0
{
found
:=
false
for
_
,
w
:=
range
warnings
{
if
contains
(
w
,
tt
.
wantWarningContains
)
{
found
=
true
break
}
}
if
!
found
{
t
.
Errorf
(
"SharedSpecValidator.Validate() warnings = %v, want warning containing %q"
,
warnings
,
tt
.
wantWarningContains
)
}
}
})
}
}
// contains checks if s contains substr
func
contains
(
s
,
substr
string
)
bool
{
return
len
(
s
)
>=
len
(
substr
)
&&
(
s
==
substr
||
len
(
substr
)
==
0
||
(
len
(
s
)
>
0
&&
len
(
substr
)
>
0
&&
findSubstring
(
s
,
substr
)))
}
func
findSubstring
(
s
,
substr
string
)
bool
{
for
i
:=
0
;
i
<=
len
(
s
)
-
len
(
substr
);
i
++
{
if
s
[
i
:
i
+
len
(
substr
)]
==
substr
{
return
true
}
}
return
false
}
examples/backends/mocker/deploy/agg.yaml
View file @
c8770464
...
...
@@ -8,7 +8,6 @@ metadata:
spec
:
services
:
Frontend
:
dynamoNamespace
:
mocker-agg
componentType
:
frontend
replicas
:
1
extraPodSpec
:
...
...
@@ -16,7 +15,6 @@ spec:
image
:
my-registry/mocker-runtime:my-tag
decode
:
envFromSecret
:
hf-token-secret
dynamoNamespace
:
mocker-agg
componentType
:
worker
subComponentType
:
decode
replicas
:
1
...
...
examples/backends/mocker/deploy/disagg.yaml
View file @
c8770464
...
...
@@ -8,7 +8,6 @@ metadata:
spec
:
services
:
Frontend
:
dynamoNamespace
:
mocker-disagg
componentType
:
frontend
replicas
:
1
extraPodSpec
:
...
...
@@ -16,7 +15,6 @@ spec:
image
:
my-registry/mocker-runtime:my-tag
prefill
:
envFromSecret
:
hf-token-secret
dynamoNamespace
:
mocker-disagg
componentType
:
worker
subComponentType
:
prefill
replicas
:
1
...
...
@@ -40,7 +38,6 @@ spec:
-
--is-prefill-worker
decode
:
envFromSecret
:
hf-token-secret
dynamoNamespace
:
mocker-disagg
componentType
:
worker
subComponentType
:
decode
replicas
:
1
...
...
examples/backends/sglang/deploy/agg.yaml
View file @
c8770464
...
...
@@ -8,7 +8,6 @@ metadata:
spec
:
services
:
Frontend
:
dynamoNamespace
:
sglang-agg
componentType
:
frontend
replicas
:
1
extraPodSpec
:
...
...
@@ -16,7 +15,6 @@ spec:
image
:
my-registry/sglang-runtime:my-tag
decode
:
envFromSecret
:
hf-token-secret
dynamoNamespace
:
sglang-agg
componentType
:
worker
replicas
:
1
resources
:
...
...
examples/backends/sglang/deploy/agg_logging.yaml
View file @
c8770464
...
...
@@ -11,7 +11,6 @@ spec:
value
:
"
1"
services
:
Frontend
:
dynamoNamespace
:
sglang-agg
componentType
:
frontend
replicas
:
1
extraPodSpec
:
...
...
@@ -19,7 +18,6 @@ spec:
image
:
my-registry/sglang-runtime:my-tag
decode
:
envFromSecret
:
hf-token-secret
dynamoNamespace
:
sglang-agg
componentType
:
worker
replicas
:
1
resources
:
...
...
examples/backends/sglang/deploy/agg_router.yaml
View file @
c8770464
...
...
@@ -8,7 +8,6 @@ metadata:
spec
:
services
:
Frontend
:
dynamoNamespace
:
sglang-agg-router
componentType
:
frontend
replicas
:
1
extraPodSpec
:
...
...
@@ -19,7 +18,6 @@ spec:
value
:
kv
decode
:
envFromSecret
:
hf-token-secret
dynamoNamespace
:
sglang-agg-router
componentType
:
worker
replicas
:
1
resources
:
...
...
examples/backends/sglang/deploy/disagg-multinode.yaml
View file @
c8770464
...
...
@@ -17,7 +17,6 @@ spec:
backendFramework
:
sglang
services
:
Frontend
:
dynamoNamespace
:
sglang-disagg-multinode
componentType
:
frontend
replicas
:
1
extraPodSpec
:
...
...
@@ -27,7 +26,6 @@ spec:
multinode
:
nodeCount
:
2
envFromSecret
:
hf-token-secret
dynamoNamespace
:
sglang-disagg-multinode
componentType
:
worker
replicas
:
1
resources
:
...
...
@@ -64,7 +62,6 @@ spec:
multinode
:
nodeCount
:
2
envFromSecret
:
hf-token-secret
dynamoNamespace
:
sglang-disagg-multinode
componentType
:
worker
replicas
:
1
resources
:
...
...
examples/backends/sglang/deploy/disagg.yaml
View file @
c8770464
...
...
@@ -8,7 +8,6 @@ metadata:
spec
:
services
:
Frontend
:
dynamoNamespace
:
sglang-disagg
componentType
:
frontend
replicas
:
1
extraPodSpec
:
...
...
@@ -16,7 +15,6 @@ spec:
image
:
my-registry/sglang-runtime:my-tag
decode
:
envFromSecret
:
hf-token-secret
dynamoNamespace
:
sglang-disagg
componentType
:
worker
subComponentType
:
decode
replicas
:
1
...
...
@@ -52,7 +50,6 @@ spec:
-
"
0.0.0.0"
prefill
:
envFromSecret
:
hf-token-secret
dynamoNamespace
:
sglang-disagg
componentType
:
worker
subComponentType
:
prefill
replicas
:
1
...
...
Prev
1
2
3
4
Next
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