Unverified Commit 97f79537 authored by Thomas Montfort's avatar Thomas Montfort Committed by GitHub
Browse files

feat(operator): managed rolling updates for DGD worker deployments (#6110)


Signed-off-by: default avatartmontfort <tmontfort@nvidia.com>
parent 0d9eb99d
/*
* 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 dynamo
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"sort"
"github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
)
// ComputeDGDWorkersSpecHash computes a deterministic hash of all worker service specs.
//
// The hash uses an exclusion-based approach: the entire DynamoComponentDeploymentSharedSpec
// is hashed after zeroing out fields that do NOT affect the pod template. This ensures
// that any new field added to the spec triggers a rolling update by default (safe by
// default), and only explicitly excluded fields are ignored.
//
// Excluded fields (do not affect the pod template):
// - ServiceName, ComponentType, SubComponentType: identity fields
// - DynamoNamespace: deprecated, not used in pod spec generation
// - Replicas: scaling, not pod template
// - Autoscaling: deprecated, ignored
// - ScalingAdapter: scaling configuration, not pod template
// - Ingress: networking resources, not pod template
// - ModelRef: headless service creation, not pod template
// - EPPConfig: EPP-only, not applicable to workers
// - Annotations, Labels: applied to K8s resources, not pod template
// (pod-level metadata is in ExtraPodMetadata which IS included)
//
// Only worker components (prefill, decode, worker) are included in the hash.
func ComputeDGDWorkersSpecHash(dgd *v1alpha1.DynamoGraphDeployment) string {
// Collect worker specs in sorted order for deterministic hashing
var workerNames []string
for name, spec := range dgd.Spec.Services {
if spec != nil && IsWorkerComponent(spec.ComponentType) {
workerNames = append(workerNames, name)
}
}
sort.Strings(workerNames)
// Build hash input map (sorted keys for determinism)
hashInputs := make(map[string]v1alpha1.DynamoComponentDeploymentSharedSpec)
for _, name := range workerNames {
spec := dgd.Spec.Services[name]
hashInputs[name] = stripNonPodTemplateFields(spec)
}
// Serialize to JSON (Go's encoding/json sorts map keys)
data, err := json.Marshal(hashInputs)
if err != nil {
// Fallback to empty hash on error (shouldn't happen with valid input)
return "00000000"
}
// Compute SHA256 and take first 8 characters for readability
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:])[:8]
}
// stripNonPodTemplateFields returns a copy of the spec with fields that do NOT affect
// the pod template zeroed out. The remaining fields are all pod-template-affecting and
// will be included in the hash.
//
// This is an exclusion-based approach: new fields added to DynamoComponentDeploymentSharedSpec
// are included in the hash by default. Only fields explicitly listed here are excluded.
func stripNonPodTemplateFields(spec *v1alpha1.DynamoComponentDeploymentSharedSpec) v1alpha1.DynamoComponentDeploymentSharedSpec {
// Start with a shallow copy of the full spec
stripped := *spec
// Zero out fields that do NOT affect the pod template.
// These are identity, scaling, networking, and metadata fields.
stripped.Annotations = nil
stripped.Labels = nil
stripped.ServiceName = ""
stripped.ComponentType = ""
stripped.SubComponentType = ""
stripped.DynamoNamespace = nil
stripped.Replicas = nil
stripped.Autoscaling = nil //nolint:staticcheck // SA1019: intentionally clearing deprecated field for backward compatibility
stripped.ScalingAdapter = nil
stripped.Ingress = nil
stripped.ModelRef = nil
stripped.EPPConfig = nil
return stripped
}
/*
* 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 dynamo
import (
"testing"
"github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
commonconsts "github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
)
func baseDGD(services map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec) *v1alpha1.DynamoGraphDeployment {
return &v1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.DynamoGraphDeploymentSpec{Services: services},
}
}
func TestComputeDGDWorkersSpecHash_Deterministic(t *testing.T) {
dgd := baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"prefill": {ComponentType: commonconsts.ComponentTypePrefill, Replicas: ptr.To(int32(2))},
"decode": {ComponentType: commonconsts.ComponentTypeDecode, Replicas: ptr.To(int32(3))},
})
h1 := ComputeDGDWorkersSpecHash(dgd)
h2 := ComputeDGDWorkersSpecHash(dgd)
assert.Equal(t, h1, h2)
assert.Len(t, h1, 8)
}
func TestComputeDGDWorkersSpecHash_IgnoresNonWorkers(t *testing.T) {
withFrontend := baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {ComponentType: commonconsts.ComponentTypeWorker},
"frontend": {ComponentType: commonconsts.ComponentTypeFrontend},
})
withoutFrontend := baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {ComponentType: commonconsts.ComponentTypeWorker},
})
assert.Equal(t, ComputeDGDWorkersSpecHash(withFrontend), ComputeDGDWorkersSpecHash(withoutFrontend))
}
func TestComputeDGDWorkersSpecHash_NoWorkers(t *testing.T) {
dgd := baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"frontend": {ComponentType: commonconsts.ComponentTypeFrontend},
})
h := ComputeDGDWorkersSpecHash(dgd)
assert.Len(t, h, 8)
}
func TestComputeDGDWorkersSpecHash_ChangesOnPodAffectingFields(t *testing.T) {
base := func() *v1alpha1.DynamoGraphDeployment {
return baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {ComponentType: commonconsts.ComponentTypeWorker},
})
}
baseHash := ComputeDGDWorkersSpecHash(base())
// Image change (via Resources)
dgd := base()
dgd.Spec.Services["worker"].Resources = &v1alpha1.Resources{
Requests: &v1alpha1.ResourceItem{CPU: "2"},
}
assert.NotEqual(t, baseHash, ComputeDGDWorkersSpecHash(dgd), "resource change should change hash")
// Env change
dgd2 := base()
dgd2.Spec.Services["worker"].Envs = []corev1.EnvVar{{Name: "FOO", Value: "bar"}}
assert.NotEqual(t, baseHash, ComputeDGDWorkersSpecHash(dgd2), "env change should change hash")
// SharedMemory change
dgd3 := base()
dgd3.Spec.Services["worker"].SharedMemory = &v1alpha1.SharedMemorySpec{
Size: resource.MustParse("1Gi"),
}
assert.NotEqual(t, baseHash, ComputeDGDWorkersSpecHash(dgd3), "shared memory change should change hash")
// GlobalDynamoNamespace change
dgd4 := base()
dgd4.Spec.Services["worker"].GlobalDynamoNamespace = true
assert.NotEqual(t, baseHash, ComputeDGDWorkersSpecHash(dgd4), "global dynamo namespace change should change hash")
}
func TestComputeDGDWorkersSpecHash_StableOnExcludedFields(t *testing.T) {
base := func() *v1alpha1.DynamoGraphDeployment {
return baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {ComponentType: commonconsts.ComponentTypeWorker},
})
}
baseHash := ComputeDGDWorkersSpecHash(base())
tests := []struct {
name string
mutate func(*v1alpha1.DynamoGraphDeployment)
}{
{"replicas", func(d *v1alpha1.DynamoGraphDeployment) {
d.Spec.Services["worker"].Replicas = ptr.To(int32(99))
}},
{"annotations", func(d *v1alpha1.DynamoGraphDeployment) {
d.Spec.Services["worker"].Annotations = map[string]string{"foo": "bar"}
}},
{"labels", func(d *v1alpha1.DynamoGraphDeployment) {
d.Spec.Services["worker"].Labels = map[string]string{"foo": "bar"}
}},
{"serviceName", func(d *v1alpha1.DynamoGraphDeployment) {
d.Spec.Services["worker"].ServiceName = "changed"
}},
{"componentType", func(d *v1alpha1.DynamoGraphDeployment) {
// Change to another worker type — still included in hash but componentType is stripped
d.Spec.Services["worker"].ComponentType = commonconsts.ComponentTypePrefill
}},
{"dynamoNamespace", func(d *v1alpha1.DynamoGraphDeployment) {
d.Spec.Services["worker"].DynamoNamespace = ptr.To("changed")
}},
{"ingress", func(d *v1alpha1.DynamoGraphDeployment) {
d.Spec.Services["worker"].Ingress = &v1alpha1.IngressSpec{Enabled: true}
}},
{"scalingAdapter", func(d *v1alpha1.DynamoGraphDeployment) {
d.Spec.Services["worker"].ScalingAdapter = &v1alpha1.ScalingAdapter{}
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dgd := base()
tt.mutate(dgd)
assert.Equal(t, baseHash, ComputeDGDWorkersSpecHash(dgd), "excluded field %s should not change hash", tt.name)
})
}
}
func TestComputeDGDWorkersSpecHash_EnvOrderMatters(t *testing.T) {
dgd1 := baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {
ComponentType: commonconsts.ComponentTypeWorker,
Envs: []corev1.EnvVar{{Name: "B", Value: "2"}, {Name: "A", Value: "1"}},
},
})
dgd2 := baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {
ComponentType: commonconsts.ComponentTypeWorker,
Envs: []corev1.EnvVar{{Name: "A", Value: "1"}, {Name: "B", Value: "2"}},
},
})
assert.NotEqual(t, ComputeDGDWorkersSpecHash(dgd1), ComputeDGDWorkersSpecHash(dgd2))
}
func TestComputeDGDWorkersSpecHash_AllWorkerTypes(t *testing.T) {
// All three worker types are included
dgd := baseDGD(map[string]*v1alpha1.DynamoComponentDeploymentSharedSpec{
"w": {ComponentType: commonconsts.ComponentTypeWorker},
"p": {ComponentType: commonconsts.ComponentTypePrefill},
"d": {ComponentType: commonconsts.ComponentTypeDecode},
})
// Changing any one of them changes the hash
base := ComputeDGDWorkersSpecHash(dgd)
dgd.Spec.Services["p"].Envs = []corev1.EnvVar{{Name: "X", Value: "1"}}
assert.NotEqual(t, base, ComputeDGDWorkersSpecHash(dgd))
}
func TestStripNonPodTemplateFields(t *testing.T) {
spec := &v1alpha1.DynamoComponentDeploymentSharedSpec{
ServiceName: "svc",
ComponentType: commonconsts.ComponentTypeWorker,
SubComponentType: "sub",
DynamoNamespace: ptr.To("ns"),
Replicas: ptr.To(int32(3)),
Autoscaling: &v1alpha1.Autoscaling{}, //nolint:staticcheck // SA1019: testing backward compatibility with deprecated field
ScalingAdapter: &v1alpha1.ScalingAdapter{},
Ingress: &v1alpha1.IngressSpec{Enabled: true},
ModelRef: &v1alpha1.ModelReference{Name: "m"},
EPPConfig: &v1alpha1.EPPConfig{},
Annotations: map[string]string{"a": "b"},
Labels: map[string]string{"c": "d"},
// Pod-affecting fields
Envs: []corev1.EnvVar{{Name: "Z"}, {Name: "A"}},
GlobalDynamoNamespace: true,
}
stripped := stripNonPodTemplateFields(spec)
// Excluded fields are zeroed
assert.Empty(t, stripped.ServiceName)
assert.Empty(t, stripped.ComponentType)
assert.Empty(t, stripped.SubComponentType)
assert.Nil(t, stripped.DynamoNamespace)
assert.Nil(t, stripped.Replicas)
assert.Nil(t, stripped.Autoscaling) //nolint:staticcheck // SA1019: testing backward compatibility with deprecated field
assert.Nil(t, stripped.ScalingAdapter)
assert.Nil(t, stripped.Ingress)
assert.Nil(t, stripped.ModelRef)
assert.Nil(t, stripped.EPPConfig)
assert.Nil(t, stripped.Annotations)
assert.Nil(t, stripped.Labels)
// Included fields are preserved
assert.True(t, stripped.GlobalDynamoNamespace)
assert.Len(t, stripped.Envs, 2)
// Envs are not sorted
assert.Equal(t, "Z", stripped.Envs[0].Name)
assert.Equal(t, "A", stripped.Envs[1].Name)
// Original spec is not mutated
assert.Equal(t, "svc", spec.ServiceName)
}
func TestSortEnvVars(t *testing.T) {
envs := []corev1.EnvVar{{Name: "C"}, {Name: "A"}, {Name: "B"}}
sorted := sortEnvVars(envs)
assert.Equal(t, "A", sorted[0].Name)
assert.Equal(t, "B", sorted[1].Name)
assert.Equal(t, "C", sorted[2].Name)
// Original not mutated
assert.Equal(t, "C", envs[0].Name)
}
......@@ -126,6 +126,11 @@ func (v *DynamoGraphDeploymentValidator) ValidateUpdate(old *nvidiacomv1alpha1.D
return warnings, err
}
// Validate no restart.id change during active rolling update
if err := v.validateNoRestartDuringRollingUpdate(old); err != nil {
return warnings, err
}
return warnings, nil
}
......@@ -472,3 +477,31 @@ func difference(a, b map[string]struct{}) []string {
}
return result
}
// validateNoRestartDuringRollingUpdate rejects restart.id changes while a rolling update is active.
func (v *DynamoGraphDeploymentValidator) validateNoRestartDuringRollingUpdate(old *nvidiacomv1alpha1.DynamoGraphDeployment) error {
// Check if a rolling update is active (Pending or InProgress)
if old.Status.RollingUpdate == nil {
return nil
}
phase := old.Status.RollingUpdate.Phase
if phase != nvidiacomv1alpha1.RollingUpdatePhasePending && phase != nvidiacomv1alpha1.RollingUpdatePhaseInProgress {
return nil
}
// Compare restart IDs
oldID := ""
if old.Spec.Restart != nil {
oldID = old.Spec.Restart.ID
}
newID := ""
if v.deployment.Spec.Restart != nil {
newID = v.deployment.Spec.Restart.ID
}
if oldID != newID {
return fmt.Errorf("spec.restart.id cannot be changed while a rolling update is %s", phase)
}
return nil
}
......@@ -1296,6 +1296,162 @@ func TestDynamoGraphDeploymentValidator_ValidateUpdate(t *testing.T) {
wantErr: true,
errMsg: "service topology is immutable and cannot be modified after creation: services added: [gateway]",
},
{
name: "restart.id change while rolling update Pending - rejected",
oldDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "old-restart-id",
},
},
Status: nvidiacomv1alpha1.DynamoGraphDeploymentStatus{
RollingUpdate: &nvidiacomv1alpha1.RollingUpdateStatus{
Phase: nvidiacomv1alpha1.RollingUpdatePhasePending,
},
},
},
newDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "new-restart-id",
},
},
},
wantErr: true,
errMsg: "spec.restart.id cannot be changed while a rolling update is Pending",
},
{
name: "restart.id change while rolling update InProgress - rejected",
oldDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "old-restart-id",
},
},
Status: nvidiacomv1alpha1.DynamoGraphDeploymentStatus{
RollingUpdate: &nvidiacomv1alpha1.RollingUpdateStatus{
Phase: nvidiacomv1alpha1.RollingUpdatePhaseInProgress,
},
},
},
newDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "new-restart-id",
},
},
},
wantErr: true,
errMsg: "spec.restart.id cannot be changed while a rolling update is InProgress",
},
{
name: "restart.id change while rolling update Completed - allowed",
oldDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "old-restart-id",
},
},
Status: nvidiacomv1alpha1.DynamoGraphDeploymentStatus{
RollingUpdate: &nvidiacomv1alpha1.RollingUpdateStatus{
Phase: nvidiacomv1alpha1.RollingUpdatePhaseCompleted,
},
},
},
newDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "new-restart-id",
},
},
},
wantErr: false,
},
{
name: "restart.id change with no rolling update - allowed",
oldDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "old-restart-id",
},
},
},
newDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "new-restart-id",
},
},
},
wantErr: false,
},
{
name: "spec change without restart.id change during rolling update - allowed",
oldDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {
Replicas: func() *int32 { r := int32(1); return &r }(),
},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "same-restart-id",
},
},
Status: nvidiacomv1alpha1.DynamoGraphDeploymentStatus{
RollingUpdate: &nvidiacomv1alpha1.RollingUpdateStatus{
Phase: nvidiacomv1alpha1.RollingUpdatePhaseInProgress,
},
},
},
newDeployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
BackendFramework: "sglang",
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"worker": {
Replicas: func() *int32 { r := int32(3); return &r }(),
},
},
Restart: &nvidiacomv1alpha1.Restart{
ID: "same-restart-id",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
......
......@@ -606,6 +606,7 @@ _Appears in:_
| `services` _object (keys:string, values:[ServiceReplicaStatus](#servicereplicastatus))_ | Services contains per-service replica status information.<br />The map key is the service name from spec.services. | | Optional: \{\} <br /> |
| `restart` _[RestartStatus](#restartstatus)_ | Restart contains the status of the restart of the graph deployment. | | Optional: \{\} <br /> |
| `checkpoints` _object (keys:string, values:[ServiceCheckpointStatus](#servicecheckpointstatus))_ | Checkpoints contains per-service checkpoint status information.<br />The map key is the service name from spec.services. | | Optional: \{\} <br /> |
| `rollingUpdate` _[RollingUpdateStatus](#rollingupdatestatus)_ | RollingUpdate tracks the progress of operator manged rolling updates.<br />Currently only supported for singl-node, non-Grove deployments (DCD/Deployment). | | Optional: \{\} <br /> |
#### DynamoModel
......@@ -950,6 +951,7 @@ _Appears in:_
| `Restarting` | |
| `Completed` | |
| `Failed` | |
| `Superseded` | |
#### RestartStatus
......@@ -1004,6 +1006,45 @@ _Appears in:_
| `Parallel` | |
#### RollingUpdatePhase
_Underlying type:_ _string_
RollingUpdatePhase represents the current phase of a rolling update.
_Validation:_
- Enum: [Pending InProgress Completed Failed ]
_Appears in:_
- [RollingUpdateStatus](#rollingupdatestatus)
| Field | Description |
| --- | --- |
| `Pending` | |
| `InProgress` | |
| `Completed` | |
| `` | |
#### RollingUpdateStatus
RollingUpdateStatus tracks the progress of a rolling update.
_Appears in:_
- [DynamoGraphDeploymentStatus](#dynamographdeploymentstatus)
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `phase` _[RollingUpdatePhase](#rollingupdatephase)_ | Phase indicates the current phase of the rolling update. | | Enum: [Pending InProgress Completed Failed ] <br />Optional: \{\} <br /> |
| `startTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#time-v1-meta)_ | StartTime is when the rolling update began. | | Optional: \{\} <br /> |
| `endTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#time-v1-meta)_ | EndTime is when the rolling update completed (successfully or failed). | | Optional: \{\} <br /> |
| `updatedServices` _string array_ | UpdatedServices is the list of services that have completed the rolling update.<br />A service is considered updated when its new replicas are all ready and old replicas are fully scaled down.<br />Only services of componentType Worker (or Prefill/Decode) are considered. | | Optional: \{\} <br /> |
#### ScalingAdapter
......@@ -1075,7 +1116,8 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `componentKind` _[ComponentKind](#componentkind)_ | ComponentKind is the underlying resource kind (e.g., "PodClique", "PodCliqueScalingGroup", "Deployment", "LeaderWorkerSet"). | | Enum: [PodClique PodCliqueScalingGroup Deployment LeaderWorkerSet] <br /> |
| `componentName` _string_ | ComponentName is the name of the underlying resource. | | |
| `componentName` _string_ | ComponentName is the name of the primary underlying resource.<br />DEPRECATED: Use ComponentNames instead. This field will be removed in a future release.<br />During rolling updates, this reflects the new (target) component name. | | |
| `componentNames` _string array_ | ComponentNames is the list of underlying resource names for this service.<br />During normal operation, this contains a single name.<br />During rolling updates, this contains both old and new component names. | | Optional: \{\} <br /> |
| `replicas` _integer_ | Replicas is the total number of non-terminated replicas.<br />Required for all component kinds. | | Minimum: 0 <br /> |
| `updatedReplicas` _integer_ | UpdatedReplicas is the number of replicas at the current/desired revision.<br />Required for all component kinds. | | Minimum: 0 <br /> |
| `readyReplicas` _integer_ | ReadyReplicas is the number of ready replicas.<br />Populated for PodClique, Deployment, and LeaderWorkerSet.<br />Not available for PodCliqueScalingGroup.<br />When nil, the field is omitted from the API response. | | Minimum: 0 <br />Optional: \{\} <br /> |
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment