Skip to content

Commit fc46702

Browse files
authored
Update evaluate InfPool readiness (#2)
- InferencePool is only one (either a ref or a embedded Spec) - Updated ensureRouterManagedResourcesAreReady to also make InfPool ready - Update logic for IsInferencePool ready to be consistent with the HTTPRoute logic and evaluate all parents to be Accepted - Removed the embedded InfPool test as it's already covered in other schenarios where `Scheduler != nil` Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
1 parent d0a3e31 commit fc46702

File tree

5 files changed

+101
-216
lines changed

5 files changed

+101
-216
lines changed

pkg/apis/serving/v1alpha1/llm_inference_service_lifecycle.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ const (
3939
)
4040

4141
const (
42-
GatewaysReady apis.ConditionType = "GatewaysReady"
43-
HTTPRoutesReady apis.ConditionType = "HTTPRoutesReady"
44-
InferencePoolsReady apis.ConditionType = "InferencePoolsReady"
42+
GatewaysReady apis.ConditionType = "GatewaysReady"
43+
HTTPRoutesReady apis.ConditionType = "HTTPRoutesReady"
44+
InferencePoolReady apis.ConditionType = "InferencePoolReady"
4545
)
4646

4747
var llmInferenceServiceCondSet = apis.NewLivingConditionSet(
@@ -141,19 +141,19 @@ func (in *LLMInferenceService) MarkHTTPRoutesNotReady(reason, messageFormat stri
141141
in.GetConditionSet().Manage(in.GetStatus()).MarkFalse(HTTPRoutesReady, reason, messageFormat, messageA...)
142142
}
143143

144-
func (in *LLMInferenceService) MarkInferencePoolsReady() {
145-
in.GetConditionSet().Manage(in.GetStatus()).MarkTrue(InferencePoolsReady)
144+
func (in *LLMInferenceService) MarkInferencePoolReady() {
145+
in.GetConditionSet().Manage(in.GetStatus()).MarkTrue(InferencePoolReady)
146146
}
147147

148-
func (in *LLMInferenceService) MarkInferencePoolsNotReady(reason, messageFormat string, messageA ...interface{}) {
149-
in.GetConditionSet().Manage(in.GetStatus()).MarkFalse(InferencePoolsReady, reason, messageFormat, messageA...)
148+
func (in *LLMInferenceService) MarkInferencePoolNotReady(reason, messageFormat string, messageA ...interface{}) {
149+
in.GetConditionSet().Manage(in.GetStatus()).MarkFalse(InferencePoolReady, reason, messageFormat, messageA...)
150150
}
151151

152152
func (in *LLMInferenceService) DetermineRouterReadiness() {
153153
subConditions := []*apis.Condition{
154154
in.GetStatus().GetCondition(GatewaysReady),
155155
in.GetStatus().GetCondition(HTTPRoutesReady),
156-
in.GetStatus().GetCondition(InferencePoolsReady),
156+
in.GetStatus().GetCondition(InferencePoolReady),
157157
in.GetStatus().GetCondition(SchedulerWorkloadReady),
158158
}
159159

pkg/controller/llmisvc/controller_int_test.go

Lines changed: 31 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package llmisvc_test
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"time"
2223

2324
"k8s.io/apimachinery/pkg/api/errors"
@@ -231,10 +232,13 @@ var _ = Describe("LLMInferenceService Controller", func() {
231232
return envTest.Client.Get(ctx, client.ObjectKey{Name: svcName + "-inference-pool", Namespace: llmSvc.GetNamespace()}, &ip)
232233
}).WithContext(ctx).Should(Succeed())
233234

234-
Eventually(LLMInferenceServiceIsReady(llmSvc)).WithContext(ctx).Should(Succeed())
235+
Eventually(LLMInferenceServiceIsReady(llmSvc, func(g Gomega, current *v1alpha1.LLMInferenceService) {
236+
g.Expect(current.Status).To(HaveCondition(string(v1alpha1.HTTPRoutesReady), "True"))
237+
g.Expect(current.Status).To(HaveCondition(string(v1alpha1.InferencePoolReady), "True"))
238+
})).WithContext(ctx).Should(Succeed())
235239
})
236240

237-
It("should reference external InferencePool", func(ctx SpecContext) {
241+
It("should use referenced external InferencePool", func(ctx SpecContext) {
238242
// given
239243
svcName := "test-llm-create-http-route-inf-pool-ref"
240244
nsName := kmeta.ChildName(svcName, "-test")
@@ -310,105 +314,12 @@ var _ = Describe("LLMInferenceService Controller", func() {
310314
Expect(expectedHTTPRoute).To(HaveBackendRefs(BackendRefInferencePool(infPoolName)))
311315
Expect(expectedHTTPRoute).To(Not(HaveBackendRefs(BackendRefService(svcName + "-kserve-workload-svc"))))
312316

313-
ensureRouterManagedResourcesAreReady(ctx, envTest.Client, llmSvc)
314-
315-
Eventually(LLMInferenceServiceIsReady(llmSvc)).WithContext(ctx).Should(Succeed())
316-
})
317-
318-
It("should evaluate InferencePool readiness conditions", func(ctx SpecContext) {
319-
// given
320-
svcName := "test-llm-infpool-conditions"
321-
nsName := kmeta.ChildName(svcName, "-test")
322-
namespace := &corev1.Namespace{
323-
ObjectMeta: metav1.ObjectMeta{
324-
Name: nsName,
325-
},
326-
}
327-
Expect(envTest.Client.Create(ctx, namespace)).To(Succeed())
328-
Expect(envTest.Client.Create(ctx, IstioShadowService(svcName, nsName))).To(Succeed())
329-
defer func() {
330-
envTest.DeleteAll(namespace)
331-
}()
332-
333-
modelURL, err := apis.ParseURL("hf://facebook/opt-125m")
334-
Expect(err).ToNot(HaveOccurred())
335-
336-
// Create the Gateway
337-
ingressGateway := DefaultGateway(nsName)
338-
Expect(envTest.Client.Create(ctx, ingressGateway)).To(Succeed())
339-
ensureGatewayReady(ctx, envTest.Client, ingressGateway)
340-
defer func() {
341-
Expect(envTest.Delete(ctx, ingressGateway)).To(Succeed())
342-
}()
343-
344-
// Create the inference pool
345-
infPoolName := kmeta.ChildName(svcName, "-my-inf-pool")
346-
infPool := InferencePool(infPoolName,
347-
InNamespace[*igwapi.InferencePool](nsName),
348-
WithSelector("app", "workload"),
349-
WithTargetPort(8000),
350-
WithExtensionRef("", "Service", kmeta.ChildName(svcName, "-epp-service")),
351-
)
352-
Expect(envTest.Create(ctx, infPool)).To(Succeed())
353-
defer func() {
354-
Expect(envTest.Delete(ctx, infPool)).To(Succeed())
355-
}()
356-
357-
// Create the llmd inference service
358-
llmSvc := &v1alpha1.LLMInferenceService{
359-
ObjectMeta: metav1.ObjectMeta{
360-
Name: svcName,
361-
Namespace: nsName,
362-
},
363-
Spec: v1alpha1.LLMInferenceServiceSpec{
364-
Model: v1alpha1.LLMModelSpec{
365-
URI: *modelURL,
366-
},
367-
WorkloadSpec: v1alpha1.WorkloadSpec{},
368-
Router: &v1alpha1.RouterSpec{
369-
Scheduler: &v1alpha1.SchedulerSpec{
370-
Pool: &v1alpha1.InferencePoolSpec{
371-
Ref: &corev1.LocalObjectReference{
372-
Name: infPoolName,
373-
},
374-
},
375-
},
376-
},
377-
},
378-
}
379-
380-
// when
381-
Expect(envTest.Create(ctx, llmSvc)).To(Succeed())
382-
defer func() {
383-
Expect(envTest.Delete(ctx, llmSvc)).To(Succeed())
384-
}()
385-
386-
// then - verify InferencePoolsReady condition is False because the pool is not ready
387-
Eventually(func(g Gomega, ctx context.Context) {
388-
current := &v1alpha1.LLMInferenceService{}
389-
g.Expect(envTest.Get(ctx, client.ObjectKeyFromObject(llmSvc), current)).To(Succeed())
390-
g.Expect(current.Status).To(HaveCondition(string(v1alpha1.InferencePoolsReady), "False"))
391-
g.Expect(current.Status).To(HaveCondition(string(v1alpha1.RouterReady), "False"))
392-
}).WithContext(ctx).Should(Succeed())
393-
394-
// when - we make the inference pool ready
395317
ensureInferencePoolReady(ctx, envTest.Client, infPool)
396-
397-
// then - verify InferencePoolsReady condition becomes True
398-
Eventually(func(g Gomega, ctx context.Context) error {
399-
current := &v1alpha1.LLMInferenceService{}
400-
g.Expect(envTest.Get(ctx, client.ObjectKeyFromObject(llmSvc), current)).To(Succeed())
401-
402-
// Check that InferencePoolsReady condition exists and is True
403-
poolsCondition := current.Status.GetCondition(v1alpha1.InferencePoolsReady)
404-
g.Expect(poolsCondition).ToNot(BeNil(), "InferencePoolsReady condition should be set")
405-
g.Expect(poolsCondition.IsTrue()).To(BeTrue(), "InferencePoolsReady condition should be True")
406-
407-
return nil
408-
}).WithContext(ctx).Should(Succeed(), "InferencePoolsReady condition should be set to True")
318+
ensureRouterManagedResourcesAreReady(ctx, envTest.Client, llmSvc)
409319

410320
Eventually(LLMInferenceServiceIsReady(llmSvc, func(g Gomega, current *v1alpha1.LLMInferenceService) {
411-
g.Expect(current.Status).To(HaveCondition(string(v1alpha1.InferencePoolsReady), "True"))
321+
g.Expect(current.Status).To(HaveCondition(string(v1alpha1.HTTPRoutesReady), "True"))
322+
g.Expect(current.Status).To(HaveCondition(string(v1alpha1.InferencePoolReady), "True"))
412323
})).WithContext(ctx).Should(Succeed())
413324
})
414325

@@ -974,49 +885,18 @@ func ensureInferencePoolReady(ctx context.Context, c client.Client, pool *igwapi
974885
return
975886
}
976887

977-
// Get the current InferencePool
978888
createdPool := &igwapi.InferencePool{}
979889
Expect(c.Get(ctx, client.ObjectKeyFromObject(pool), createdPool)).To(Succeed())
980-
981-
// Set the status conditions to simulate the controller making the InferencePool ready
982-
createdPool.Status.Parents = []igwapi.PoolStatus{
983-
{
984-
GatewayRef: corev1.ObjectReference{
985-
Name: "kserve-ingress-gateway", // Example gateway
986-
},
987-
Conditions: []metav1.Condition{
988-
{
989-
Type: "Accepted",
990-
Status: metav1.ConditionTrue,
991-
Reason: "Accepted",
992-
Message: "InferencePool accepted",
993-
LastTransitionTime: metav1.Now(),
994-
},
995-
},
996-
},
997-
}
998-
999-
// Update the status
890+
WithInferencePoolReadyStatus()(createdPool)
1000891
Expect(c.Status().Update(ctx, createdPool)).To(Succeed())
1001892

1002893
// Verify the InferencePool is now ready
894+
updatedPool := &igwapi.InferencePool{}
1003895
Eventually(func(g Gomega, ctx context.Context) bool {
1004-
updatedPool := &igwapi.InferencePool{}
896+
updatedPool = &igwapi.InferencePool{}
1005897
g.Expect(c.Get(ctx, client.ObjectKeyFromObject(pool), updatedPool)).To(Succeed())
1006-
isReady := false
1007-
for _, parent := range updatedPool.Status.Parents {
1008-
for _, cond := range parent.Conditions {
1009-
if cond.Type == "Accepted" && cond.Status == metav1.ConditionTrue {
1010-
isReady = true
1011-
break
1012-
}
1013-
}
1014-
if isReady {
1015-
break
1016-
}
1017-
}
1018-
return isReady
1019-
}).WithContext(ctx).Should(BeTrue())
898+
return llmisvc.IsInferencePoolReady(updatedPool)
899+
}).WithContext(ctx).Should(BeTrue(), fmt.Sprintf("Expected InferencePool to be ready, got: %#v", updatedPool.Status))
1020900
}
1021901

1022902
// Only runs in non-cluster mode
@@ -1063,6 +943,23 @@ func ensureRouterManagedResourcesAreReady(ctx context.Context, c client.Client,
1063943
// Ensure at least one HTTPRoute was found and made ready
1064944
g.Expect(httpRoutes.Items).To(gomega.HaveLen(1), "Expected exactly one managed HTTPRoute")
1065945

946+
infPoolsListOpts := &client.ListOptions{
947+
Namespace: llmSvc.Namespace,
948+
LabelSelector: labels.SelectorFromSet(llmisvc.SchedulerLabels(llmSvc)),
949+
}
950+
951+
infPools := &igwapi.InferencePoolList{}
952+
err = c.List(ctx, infPools, infPoolsListOpts)
953+
if err != nil && !errors.IsNotFound(err) {
954+
g.Expect(err).NotTo(gomega.HaveOccurred())
955+
}
956+
logf.FromContext(ctx).Info("Marking InferencePool resources ready", "inferencepools", infPools)
957+
for _, pool := range infPools.Items {
958+
updatedPool := pool.DeepCopy()
959+
WithInferencePoolReadyStatus()(updatedPool)
960+
g.Expect(c.Status().Update(ctx, updatedPool)).To(gomega.Succeed())
961+
}
962+
1066963
ensureSchedulerDeploymentReady(ctx, c, llmSvc)
1067964
}).WithContext(ctx).Should(gomega.Succeed())
1068965
}

pkg/controller/llmisvc/fixture/gwapi_builders.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,10 @@ func WithInferencePoolReadyStatus() InferencePoolOption {
591591
{
592592
Conditions: []metav1.Condition{
593593
{
594-
Type: string(igwapi.InferencePoolConditionAccepted),
595-
Status: metav1.ConditionTrue,
596-
Reason: string(igwapi.InferencePoolReasonAccepted),
594+
Type: string(igwapi.InferencePoolConditionAccepted),
595+
Status: metav1.ConditionTrue,
596+
Reason: string(igwapi.InferencePoolReasonAccepted),
597+
LastTransitionTime: metav1.Now(),
597598
},
598599
},
599600
},

pkg/controller/llmisvc/router.go

Lines changed: 36 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func (r *LLMInferenceServiceReconciler) reconcileRouter(ctx context.Context, llm
6767
}
6868

6969
// Evaluate the subconditions
70+
if err := r.EvaluateInferencePoolConditions(ctx, llmSvc); err != nil {
71+
return fmt.Errorf("failed to evaluate Inference Pool conditions: %w", err)
72+
}
73+
7074
if err := r.EvaluateGatewayConditions(ctx, llmSvc); err != nil {
7175
return fmt.Errorf("failed to evaluate gateway conditions: %w", err)
7276
}
@@ -75,10 +79,6 @@ func (r *LLMInferenceServiceReconciler) reconcileRouter(ctx context.Context, llm
7579
return fmt.Errorf("failed to evaluate HTTPRoute conditions: %w", err)
7680
}
7781

78-
if err := r.EvaluateInferencePoolConditions(ctx, llmSvc); err != nil {
79-
return fmt.Errorf("failed to evaluate Inference Pool conditions: %w", err)
80-
}
81-
8282
return nil
8383
}
8484

@@ -378,72 +378,56 @@ func (r *LLMInferenceServiceReconciler) EvaluateHTTPRouteConditions(ctx context.
378378
}
379379

380380
// EvaluateInferencePoolConditions evaluates the readiness of all Inference Pools in the LLMInferenceService
381-
// and updates the InferencePoolsReady condition accordingly
381+
// and updates the InferencePoolReady condition accordingly
382382
func (r *LLMInferenceServiceReconciler) EvaluateInferencePoolConditions(ctx context.Context, llmSvc *v1alpha1.LLMInferenceService) error {
383383
logger := log.FromContext(ctx).WithName("EvaluateInferencePoolConditions")
384384

385385
// If no router or scheduler configuration, mark Inference Pools as ready (no Inference Pools to evaluate)
386-
if llmSvc.Spec.Router == nil || llmSvc.Spec.Router.Scheduler == nil || llmSvc.Spec.Router.Scheduler.Pool == nil {
387-
logger.V(2).Info("No Router or Inference Pool configuration found, marking InferencePoolsReady as True")
388-
llmSvc.MarkInferencePoolsReady()
386+
if llmSvc.Spec.Router == nil || llmSvc.Spec.Router.Scheduler == nil {
387+
logger.V(2).Info("Scheduler is disabled, marking InferencePoolReady as True")
388+
llmSvc.MarkInferencePoolReady()
389389
return nil
390390
}
391391

392-
// Collect all Inference Pools (both referenced and embedded)
393-
var allPools []*igwapi.InferencePool
392+
curr := &igwapi.InferencePool{}
394393

395-
// Get the referenced inference pool
396-
referencedPool := &igwapi.InferencePool{}
397-
if llmSvc.Spec.Router.Scheduler.Pool.Ref != nil {
394+
if llmSvc.Spec.Router.Scheduler.Pool != nil && llmSvc.Spec.Router.Scheduler.Pool.Ref != nil && llmSvc.Spec.Router.Scheduler.Pool.Ref.Name != "" {
398395
poolRef := llmSvc.Spec.Router.Scheduler.Pool.Ref
399-
400-
err := r.Client.Get(ctx, types.NamespacedName{
401-
Namespace: llmSvc.Namespace,
402-
Name: poolRef.Name,
403-
}, referencedPool)
396+
err := r.Client.Get(ctx, types.NamespacedName{Namespace: llmSvc.Namespace, Name: poolRef.Name}, curr)
404397
if err != nil {
405-
msg := fmt.Sprintf("Failed to fetch referenced Inference Pool: %v", err)
406-
llmSvc.MarkInferencePoolsNotReady("InferencePoolFetchError", msg)
407-
return fmt.Errorf(msg, err)
398+
err := fmt.Errorf("failed to fetch referenced Inference Pool %s/%s: %w", llmSvc.Namespace, poolRef.Name, err)
399+
llmSvc.MarkInferencePoolNotReady("InferencePoolFetchError", err.Error())
400+
return err
408401
}
409-
}
410-
allPools = append(allPools, referencedPool)
411-
412-
// Check if the LLMInferenceService has an embedded Inference Pool spec
413-
if llmSvc.Spec.Router.Scheduler.Pool.Spec != nil {
414-
embeddedPool := &igwapi.InferencePool{}
415-
err := r.Client.Get(ctx, types.NamespacedName{
416-
Name: llmSvc.Name + "-pool",
417-
Namespace: llmSvc.Namespace,
418-
}, embeddedPool)
402+
} else {
403+
expected := r.expectedSchedulerInferencePool(ctx, llmSvc)
404+
err := r.Client.Get(ctx, types.NamespacedName{Namespace: expected.Namespace, Name: expected.Name}, curr)
419405
if err != nil {
420-
msg := fmt.Sprintf("Failed to fetch embedded Inference Pool: %v", err)
421-
llmSvc.MarkInferencePoolsNotReady("InferencePoolFetchError", msg)
422-
return fmt.Errorf(msg, err)
406+
err := fmt.Errorf("failed to fetch embedded Inference Pool %s/%s: %w", llmSvc.Namespace, llmSvc.Name, err)
407+
llmSvc.MarkInferencePoolNotReady("InferencePoolFetchError", err.Error())
408+
return err
423409
}
424-
allPools = append(allPools, embeddedPool)
425-
}
426-
427-
// If the list is empty at this point, mark as ready since there is nothing to evaluate
428-
if len(allPools) == 0 {
429-
logger.V(2).Info("No Inference Pools found, marking InferencePoolsReady as True")
430-
llmSvc.MarkInferencePoolsReady()
431-
return nil
432410
}
433411

434-
// Determine which inference pools are not ready
435-
notReadyPools := EvaluateInferencePoolReadiness(ctx, allPools)
436-
if len(notReadyPools) > 0 {
437-
poolNames := make([]string, len(notReadyPools))
438-
for i, route := range notReadyPools {
439-
poolNames[i] = fmt.Sprintf("%s/%s", route.Namespace, route.Name)
412+
if !IsInferencePoolReady(curr) {
413+
topLevelCondition, _ := nonReadyInferencePoolTopLevelCondition(curr)
414+
if topLevelCondition != nil {
415+
llmSvc.MarkInferencePoolNotReady("InferencePoolNotReady", fmt.Sprintf(
416+
"%s/%s: %v=%#v (reason %q, message %q)",
417+
curr.Namespace,
418+
curr.Name,
419+
topLevelCondition.Type,
420+
topLevelCondition.Status,
421+
topLevelCondition.Reason,
422+
topLevelCondition.Message,
423+
))
424+
} else {
425+
llmSvc.MarkInferencePoolNotReady("InferencePoolNotReady", fmt.Sprintf("The inference pool %s/%s is not ready", curr.Namespace, curr.Name))
440426
}
441-
llmSvc.MarkInferencePoolsNotReady("InferencePoolsNotReady", "The following Inference Pools are not ready: %v", poolNames)
442-
logger.V(2).Info("Some Inference Pools are not ready", "pools", notReadyPools)
443427
return nil
444428
}
445429

446-
llmSvc.MarkInferencePoolsReady()
447-
logger.V(2).Info("All Inference Pools are ready", "pools", allPools)
430+
llmSvc.MarkInferencePoolReady()
431+
logger.V(2).Info("Inference Pool is ready", "pool", curr)
448432
return nil
449433
}

0 commit comments

Comments
 (0)