@@ -3,6 +3,9 @@ package fixtures
33import (
44 "context"
55 "fmt"
6+ "os"
7+ "reflect"
8+ "strings"
69
710 apierrors "k8s.io/apimachinery/pkg/api/errors"
811 "k8s.io/apimachinery/pkg/api/meta"
@@ -25,11 +28,38 @@ var inferenceObjectiveGVR = schema.GroupVersionResource{
2528// Returns applied=true if the object exists or was created. If the InferenceObjective API is not
2629// available on the cluster, returns (false, nil).
2730func EnsureInferenceObjective (ctx context.Context , dc dynamic.Interface , namespace , poolName string ) (applied bool , err error ) {
28- obj := buildInferenceObjective (namespace , poolName )
2931 ri := dc .Resource (inferenceObjectiveGVR ).Namespace (namespace )
32+ poolGroup , gErr := resolveInferencePoolGroup (ctx , dc , namespace , poolName )
33+ if gErr != nil {
34+ return false , gErr
35+ }
36+ obj := buildInferenceObjective (namespace , poolName , poolGroup )
3037
3138 if _ , cErr := ri .Create (ctx , obj , metav1.CreateOptions {}); cErr != nil {
3239 if apierrors .IsAlreadyExists (cErr ) {
40+ current , getErr := ri .Get (ctx , "e2e-default" , metav1.GetOptions {})
41+ if getErr != nil {
42+ return false , fmt .Errorf ("get existing InferenceObjective e2e-default: %w" , getErr )
43+ }
44+
45+ currentSpec , _ , specErr := unstructured .NestedMap (current .Object , "spec" )
46+ if specErr != nil {
47+ return false , fmt .Errorf ("read existing InferenceObjective spec: %w" , specErr )
48+ }
49+ desiredSpec , _ , desiredErr := unstructured .NestedMap (obj .Object , "spec" )
50+ if desiredErr != nil {
51+ return false , fmt .Errorf ("read desired InferenceObjective spec: %w" , desiredErr )
52+ }
53+ if reflect .DeepEqual (currentSpec , desiredSpec ) {
54+ return true , nil
55+ }
56+
57+ if setErr := unstructured .SetNestedMap (current .Object , desiredSpec , "spec" ); setErr != nil {
58+ return false , fmt .Errorf ("set desired InferenceObjective spec: %w" , setErr )
59+ }
60+ if _ , uErr := ri .Update (ctx , current , metav1.UpdateOptions {}); uErr != nil {
61+ return false , fmt .Errorf ("update InferenceObjective e2e-default: %w" , uErr )
62+ }
3363 return true , nil
3464 }
3565 if inferenceObjectiveAPIMissing (cErr ) {
@@ -49,7 +79,7 @@ func DeleteInferenceObjective(ctx context.Context, dc dynamic.Interface, namespa
4979 return err
5080}
5181
52- func buildInferenceObjective (namespace , poolName string ) * unstructured.Unstructured {
82+ func buildInferenceObjective (namespace , poolName , poolGroup string ) * unstructured.Unstructured {
5383 return & unstructured.Unstructured {
5484 Object : map [string ]interface {}{
5585 "apiVersion" : "inference.networking.x-k8s.io/v1alpha2" ,
@@ -63,7 +93,7 @@ func buildInferenceObjective(namespace, poolName string) *unstructured.Unstructu
6393 "poolRef" : map [string ]interface {}{
6494 "name" : poolName ,
6595 "kind" : "InferencePool" ,
66- "group" : "inference.networking.k8s.io" ,
96+ "group" : poolGroup ,
6797 },
6898 },
6999 },
@@ -77,9 +107,44 @@ func inferenceObjectiveAPIMissing(err error) bool {
77107 if meta .IsNoMatchError (err ) {
78108 return true
79109 }
80- if apierrors .IsNotFound (err ) {
110+ se , ok := err .(* apierrors.StatusError )
111+ if ! ok {
112+ return false
113+ }
114+ if se .ErrStatus .Reason != metav1 .StatusReasonNotFound {
115+ return false
116+ }
117+ if strings .Contains (strings .ToLower (se .ErrStatus .Message ), "the server could not find the requested resource" ) {
81118 return true
82119 }
83- // Discovery / dynamic client sometimes returns ResourceNotFound with Reason NotFound
84- return apierrors .ReasonForError (err ) == metav1 .StatusReasonNotFound
120+ details := se .ErrStatus .Details
121+ if details == nil {
122+ return false
123+ }
124+ return details .Group == inferenceObjectiveGVR .Group && details .Kind == inferenceObjectiveGVR .Resource
125+ }
126+
127+ func resolveInferencePoolGroup (ctx context.Context , dc dynamic.Interface , namespace , poolName string ) (string , error ) {
128+ if envPoolGroup := os .Getenv ("POOL_GROUP" ); envPoolGroup != "" {
129+ return envPoolGroup , nil
130+ }
131+
132+ inferencePoolCandidates := []schema.GroupVersionResource {
133+ {Group : "inference.networking.k8s.io" , Version : "v1" , Resource : "inferencepools" },
134+ {Group : "inference.networking.x-k8s.io" , Version : "v1alpha2" , Resource : "inferencepools" },
135+ }
136+
137+ for _ , gvr := range inferencePoolCandidates {
138+ _ , err := dc .Resource (gvr ).Namespace (namespace ).Get (ctx , poolName , metav1.GetOptions {})
139+ if err == nil {
140+ return gvr .Group , nil
141+ }
142+ if apierrors .IsNotFound (err ) || meta .IsNoMatchError (err ) {
143+ continue
144+ }
145+ return "" , fmt .Errorf ("detect InferencePool API group for %q: %w" , poolName , err )
146+ }
147+
148+ // Default to the primary group used by llm-d charts.
149+ return "inference.networking.k8s.io" , nil
85150}
0 commit comments