Skip to content

Support modelroute and modelserver status#1292

Open
StLeoX wants to merge 12 commits into
volcano-sh:mainfrom
StLeoX:feat/modelroute-status
Open

Support modelroute and modelserver status#1292
StLeoX wants to merge 12 commits into
volcano-sh:mainfrom
StLeoX:feat/modelroute-status

Conversation

@StLeoX

@StLeoX StLeoX commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

close #1257

Result

I manually tested

2026-07-03_22-15

For reviewers: charts/, client-go/ are auto generated, no need to review.

Copilot AI review requested due to automatic review settings July 3, 2026 14:22
@volcano-sh-bot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign lizhencheng9527 for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces status tracking for ModelRoute and ModelServer resources by adding fields like observedGeneration, conditions, readyReplicas, and matchedReplicas to their CRDs, API definitions, and generated client-go apply configurations. The ModelRouteController and ModelServerController are updated to reconcile and write these statuses, backed by new unit tests. The review feedback highlights critical improvements to controller reliability: first, status updates should be aborted if the resource's generation changes during the API fetch to prevent writing stale status; second, the ModelServer status update should be deferred to the end of the sync handler so that non-critical API write failures do not block critical datastore and pod binding updates.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +172 to +181
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
// Get latest version from API server to avoid conflicts
ctx := context.Background()
latest, err := c.kthenaClient.NetworkingV1alpha1().ModelRoutes(mr.Namespace).Get(ctx, mr.Name, metav1.GetOptions{})
if err != nil {
return err
}

mrCopy := latest.DeepCopy()
mrCopy.Status.ObservedGeneration = latest.Generation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the ModelRoute spec generation has changed since we fetched it, we should abort the status update. Otherwise, we might write a stale status or set an incorrect ObservedGeneration for the new spec. Since a new sync event is guaranteed to be queued for the updated spec, returning nil here is safe and prevents race conditions.

	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
		// Get latest version from API server to avoid conflicts
		ctx := context.Background()
		latest, err := c.kthenaClient.NetworkingV1alpha1().ModelRoutes(mr.Namespace).Get(ctx, mr.Name, metav1.GetOptions{})
		if err != nil {
			return err
		}

		if latest.Generation != mr.Generation {
			return nil
		}

		mrCopy := latest.DeepCopy()
		mrCopy.Status.ObservedGeneration = latest.Generation

Comment on lines +218 to +225
if err := c.store.AddOrUpdateModelServer(ms, pods); err != nil {
return err
}

if err := c.updateModelServerStatus(ms, podList, pods); err != nil {
klog.Errorf("Failed to update ModelServer status for %s/%s: %v", ms.Namespace, ms.Name, err)
return err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Updating the ModelServer status is a non-critical API server write, whereas updating the datastore and pod-to-server bindings (lines 227-267) is critical for the router's traffic routing functionality. If updateModelServerStatus fails here, the entire sync handler returns early, skipping the critical pod binding updates. We should perform the status update at the very end of syncModelServerHandler to ensure all datastore updates are completed first.

Suggested change
if err := c.store.AddOrUpdateModelServer(ms, pods); err != nil {
return err
}
if err := c.updateModelServerStatus(ms, podList, pods); err != nil {
klog.Errorf("Failed to update ModelServer status for %s/%s: %v", ms.Namespace, ms.Name, err)
return err
}
if err := c.store.AddOrUpdateModelServer(ms, pods); err != nil {
return err
}

@@ -253,6 +267,59 @@ func (c *ModelServerController) syncModelServerHandler(key string) error {
return nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Perform the ModelServer status update at the end of the sync handler, after all datastore and pod binding updates have been successfully completed.

Suggested change
return nil
return c.updateModelServerStatus(ms, podList, pods)

Comment on lines +289 to +298
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
// Get latest version from API server to avoid conflicts
ctx := context.Background()
latest, err := c.kthenaClient.NetworkingV1alpha1().ModelServers(ms.Namespace).Get(ctx, ms.Name, metav1.GetOptions{})
if err != nil {
return err
}

msCopy := latest.DeepCopy()
msCopy.Status.ObservedGeneration = latest.Generation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the ModelServer spec generation has changed since we fetched it, we should abort the status update. Otherwise, we might write stale status metrics (like MatchedReplicas and ReadyReplicas computed for the old spec) and mark them as observing the new generation. Since a new sync event is guaranteed to be queued for the updated spec, returning nil here is safe and prevents race conditions.

	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
		// Get latest version from API server to avoid conflicts
		ctx := context.Background()
		latest, err := c.kthenaClient.NetworkingV1alpha1().ModelServers(ms.Namespace).Get(ctx, ms.Name, metav1.GetOptions{})
		if err != nil {
			return err
		}

		if latest.Generation != ms.Generation {
			return nil
		}

		msCopy := latest.DeepCopy()
		msCopy.Status.ObservedGeneration = latest.Generation

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds observable status fields to the ModelServer and ModelRoute CRDs and updates the kthena-router controllers to populate those status fields, enabling readiness/observability workflows like kubectl wait and status-driven automation.

Changes:

  • Add ObservedGeneration and Conditions to ModelRouteStatus, and ObservedGeneration/replica counts/Conditions to ModelServerStatus.
  • Update router controllers to write CR status via the typed kthena client (with conflict retries) and to re-enqueue ModelServers on relevant Pod lifecycle events.
  • Regenerate CRDs, docs, deepcopy, and client-go applyconfiguration outputs to reflect the new status fields.

Reviewed changes

Copilot reviewed 10 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/kthena-router/controller/modelserver_controller.go Update ModelServer controller to compute pod readiness and write ModelServer status/conditions; enqueue ModelServers on pod events.
pkg/kthena-router/controller/modelserver_controller_test.go Update controller constructor usage; add tests for pod→modelserver enqueue behavior and selector matching helper.
pkg/kthena-router/controller/modelroute_controller.go Update ModelRoute controller to write Ready condition and observedGeneration via UpdateStatus with retry-on-conflict.
pkg/kthena-router/controller/modelroute_controller_test.go Update constructor usage and add status update tests for ModelRoute.
pkg/apis/networking/v1alpha1/modelserver_types.go Define new ModelServerStatus fields (observedGeneration/replica counts/conditions).
pkg/apis/networking/v1alpha1/modelroute_types.go Define new ModelRouteStatus fields (observedGeneration/conditions).
pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go Regenerate deepcopy logic for new status fields (conditions deep copy).
cmd/kthena-router/app/controller.go Wire kthena client into router controllers.
docs/kthena/docs/reference/crd/networking.serving.volcano.sh.md Regenerated CRD reference docs reflecting new status fields.
charts/kthena/charts/networking/crds/networking.serving.volcano.sh_modelservers.yaml Regenerated ModelServer CRD schema to include status fields (conditions/replicas/observedGeneration).
charts/kthena/charts/networking/crds/networking.serving.volcano.sh_modelroutes.yaml Regenerated ModelRoute CRD schema to include status fields (conditions/observedGeneration).
client-go/applyconfiguration/utils.go Regenerated applyconfiguration kind mapping to include new *Status apply configs.
client-go/applyconfiguration/networking/v1alpha1/modelserverstatus.go Generated applyconfiguration type for ModelServerStatus.
client-go/applyconfiguration/networking/v1alpha1/modelserver.go Regenerated ModelServer applyconfiguration to use ModelServerStatusApplyConfiguration.
client-go/applyconfiguration/networking/v1alpha1/modelroutestatus.go Generated applyconfiguration type for ModelRouteStatus.
client-go/applyconfiguration/networking/v1alpha1/modelroute.go Regenerated ModelRoute applyconfiguration to use ModelRouteStatusApplyConfiguration.
Files not reviewed (6)
  • client-go/applyconfiguration/networking/v1alpha1/modelroute.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelroutestatus.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelserver.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelserverstatus.go: Generated file
  • client-go/applyconfiguration/utils.go: Generated file
  • pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go: Generated file
Comments suppressed due to low confidence (1)

pkg/kthena-router/controller/modelserver_controller.go:85

  • NewModelServerController now requires a kthenaClient parameter. There is existing controller documentation under pkg/kthena-router/controller/README.md that still shows the old constructor signature (missing kthenaClient), which will be confusing for contributors.
func NewModelServerController(
	kthenaClient kthenaclient.Interface,
	kthenaInformerFactory informersv1alpha1.SharedInformerFactory,
	kubeInformerFactory informers.SharedInformerFactory,
	store datastore.Store,
) *ModelServerController {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +297 to +316
msCopy := latest.DeepCopy()
msCopy.Status.ObservedGeneration = latest.Generation
msCopy.Status.MatchedReplicas = int32(len(podList))
msCopy.Status.ReadyReplicas = int32(readyPods.Len())

if readyPods.Len() > 0 {
meta.SetStatusCondition(&msCopy.Status.Conditions, metav1.Condition{
Type: "Ready",
Status: metav1.ConditionTrue,
Reason: "PodsReady",
Message: fmt.Sprintf("%d ready pods matched", readyPods.Len()),
})
} else {
meta.SetStatusCondition(&msCopy.Status.Conditions, metav1.Condition{
Type: "Ready",
Status: metav1.ConditionFalse,
Reason: "NoReadyPods",
Message: "No ready pods match workloadSelector",
})
}
Comment on lines +180 to +188
mrCopy := latest.DeepCopy()
mrCopy.Status.ObservedGeneration = latest.Generation

meta.SetStatusCondition(&mrCopy.Status.Conditions, metav1.Condition{
Type: "Ready",
Status: metav1.ConditionTrue,
Reason: "RouteRegistered",
Message: "ModelRoute registered in router store",
})
Comment on lines +1121 to +1128
// TestModelServerController_PodEnqueuesModelServer verifies that syncPodHandler
// enqueues matching ModelServers after pod state changes.
func TestModelServerController_PodEnqueuesModelServer(t *testing.T) {
kubeClient := kubefake.NewSimpleClientset()
kthenaClient := kthenafake.NewSimpleClientset()

ms := &aiv1alpha1.ModelServer{
ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "test-ms-enq"},
vivek41-glitch and others added 7 commits July 3, 2026 22:49
…or handling

Signed-off-by: Leo Xie <stleox@qq.com>
Signed-off-by: Leo Xie <stleox@qq.com>
Signed-off-by: Leo Xie <stleox@qq.com>
Signed-off-by: Leo Xie <stleox@qq.com>
@StLeoX StLeoX force-pushed the feat/modelroute-status branch from 7125d78 to e739430 Compare July 3, 2026 14:50
Comment on lines +153 to +158
ReadyReplicas int32 `json:"readyReplicas,omitempty"`

// matchedReplicas is the total number of pods (ready or not) that match the
// workloadSelector.
// +optional
MatchedReplicas int32 `json:"matchedReplicas,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems not appropriate to represent pd disaggregated case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I want to define two conditions in ModelServerStatus.Condition: 1. At least one pod is ready, 2. All pods are ready. That's enough for steady-state determination during testing. If we want to support PD disaggregated semantics, i can create another issue.

Comment thread pkg/apis/networking/v1alpha1/modelroute_types.go
Comment thread pkg/kthena-router/controller/modelroute_controller.go Outdated
StLeoX added 2 commits July 9, 2026 15:51
Signed-off-by: Leo Xie <stleox@qq.com>
Signed-off-by: Leo Xie <stleox@qq.com>
Copilot AI review requested due to automatic review settings July 9, 2026 07:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 16 changed files in this pull request and generated 2 comments.

Files not reviewed (6)
  • client-go/applyconfiguration/networking/v1alpha1/modelroute.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelroutestatus.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelserver.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelserverstatus.go: Generated file
  • client-go/applyconfiguration/utils.go: Generated file
  • pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go: Generated file

Comment on lines +227 to +229
if err := c.store.AddOrUpdateModelServer(ms, pods); err != nil {
return err
}
Comment on lines 359 to 365
if errors.IsNotFound(err) {
_ = c.store.DeletePod(types.NamespacedName{Namespace: namespace, Name: name})
// When a pod is deleted, enqueue all ModelServers in the namespace
// to refresh their status (we no longer have the pod to check labels).
c.enqueueModelServersInNamespace(namespace)
return nil
}
# Conflicts:
#	cmd/kthena-router/app/controller.go
#	pkg/kthena-router/controller/modelroute_controller_test.go
#	pkg/kthena-router/controller/modelserver_controller_test.go
Copilot AI review requested due to automatic review settings July 9, 2026 08:23
@volcano-sh-bot

Copy link
Copy Markdown
Contributor

Adding label do-not-merge/contains-merge-commits because PR contains merge commits, which are not allowed in this repository.
Use git rebase to reapply your commits on top of the target branch. Detailed instructions for doing so can be found here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 16 changed files in this pull request and generated 12 comments.

Files not reviewed (6)
  • client-go/applyconfiguration/networking/v1alpha1/modelroute.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelroutestatus.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelserver.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelserverstatus.go: Generated file
  • client-go/applyconfiguration/utils.go: Generated file
  • pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go: Generated file

Comment on lines +319 to +322
msCopy := latest.DeepCopy()
msCopy.Status.ObservedGeneration = latest.Generation
msCopy.Status.MatchedReplicas = matchedReplicas
msCopy.Status.ReadyReplicas = readyReplicas
Comment on lines +324 to +336
meta.SetStatusCondition(&msCopy.Status.Conditions, metav1.Condition{
Type: string(aiv1alpha1.ModelServerConditionReady),
Status: readyStatus,
Reason: readyReason,
Message: readyMessage,
})

meta.SetStatusCondition(&msCopy.Status.Conditions, metav1.Condition{
Type: string(aiv1alpha1.ModelServerConditionAllPodsReady),
Status: allPodsReadyStatus,
Reason: allPodsReadyReason,
Message: allPodsReadyMessage,
})
Comment on lines +184 to +186
mrCopy := latest.DeepCopy()
mrCopy.Status.ObservedGeneration = latest.Generation

Comment on lines +187 to +192
meta.SetStatusCondition(&mrCopy.Status.Conditions, metav1.Condition{
Type: string(aiv1alpha1.ModelRouteConditionReady),
Status: metav1.ConditionTrue,
Reason: "RouteRegistered",
Message: "ModelRoute registered in router store",
})
Comment on lines 351 to 356
if errors.IsNotFound(err) {
_ = c.store.DeletePod(types.NamespacedName{Namespace: namespace, Name: name})
// When a pod is deleted, enqueue all ModelServers in the namespace
// to refresh their status (we no longer have the pod to check labels).
c.enqueueModelServersInNamespace(namespace)
return nil
kubeInformerFactory := informers.NewSharedInformerFactory(kubeClient, 0)
kthenaInformerFactory := informersv1alpha1.NewSharedInformerFactory(kthenaClient, 0)
store := newStoreWithMockBackend()
controller := NewModelServerController(kthenaClient, kthenaInformerFactory, kubeInformerFactory, store)
kubeInformerFactory := informers.NewSharedInformerFactory(kubeClient, 0)
kthenaInformerFactory := informersv1alpha1.NewSharedInformerFactory(kthenaClient, 0)
store := newStoreWithMockBackend()
controller := NewModelServerController(kthenaClient, kthenaInformerFactory, kubeInformerFactory, store)
kthenaInformerFactory := informersv1alpha1.NewSharedInformerFactory(kthenaClient, 0)
store := datastore.New()

controller := NewModelRouteController(kthenaClient, kthenaInformerFactory, store)
Comment on lines +193 to +195
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `observedGeneration` _integer_ | observedGeneration is the most recent generation observed for this ModelRoute. | | |
Comment on lines +268 to +272
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `observedGeneration` _integer_ | observedGeneration is the most recent generation observed for this ModelServer. | | |
| `readyReplicas` _integer_ | readyReplicas is the number of ready pods that are currently matched by the<br />workloadSelector and have been successfully registered in the router's store. | | |
| `matchedReplicas` _integer_ | matchedReplicas is the total number of pods (ready or not) that match the<br />workloadSelector. | | |
Signed-off-by: Leo Xie <stleox@qq.com>
Copilot AI review requested due to automatic review settings July 10, 2026 09:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 16 changed files in this pull request and generated 4 comments.

Files not reviewed (6)
  • client-go/applyconfiguration/networking/v1alpha1/modelroute.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelroutestatus.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelserver.go: Generated file
  • client-go/applyconfiguration/networking/v1alpha1/modelserverstatus.go: Generated file
  • client-go/applyconfiguration/utils.go: Generated file
  • pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go: Generated file

Comment on lines +192 to +197
meta.SetStatusCondition(&mrCopy.Status.Conditions, metav1.Condition{
Type: string(aiv1alpha1.ModelRouteConditionReady),
Status: metav1.ConditionTrue,
Reason: aiv1alpha1.ReasonRouteRegistered,
Message: "ModelRoute registered in router store",
})
Comment on lines +369 to +374
if err := c.addOrUpdatePod(pod); err != nil {
return err
}
// Pod became ready: enqueue matching ModelServers to refresh status
c.enqueueMatchingModelServers(pod)
return nil
Comment on lines +193 to +195
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `observedGeneration` _integer_ | observedGeneration is the most recent generation observed for this ModelRoute. | | |
Comment on lines +268 to +272
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `observedGeneration` _integer_ | observedGeneration is the most recent generation observed for this ModelServer. | | |
| `readyReplicas` _integer_ | readyReplicas is the number of ready pods that are currently matched by the<br />workloadSelector and have been successfully registered in the router's store. | | |
| `matchedReplicas` _integer_ | matchedReplicas is the total number of pods (ready or not) that match the<br />workloadSelector. | | |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ModelServer CRD lacks observable status — kubectl wait and readiness checks are impossible

5 participants