@@ -18,8 +18,8 @@ package controller
18
18
19
19
import (
20
20
"context"
21
+ "fmt"
21
22
22
- "github.com/go-logr/logr"
23
23
"k8s.io/apimachinery/pkg/api/errors"
24
24
"k8s.io/apimachinery/pkg/runtime"
25
25
"k8s.io/apimachinery/pkg/types"
@@ -34,6 +34,10 @@ import (
34
34
logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
35
35
)
36
36
37
+ const (
38
+ modelNameKey = "spec.modelName"
39
+ )
40
+
37
41
type InferenceModelReconciler struct {
38
42
client.Client
39
43
Scheme * runtime.Scheme
@@ -43,44 +47,103 @@ type InferenceModelReconciler struct {
43
47
}
44
48
45
49
func (c * InferenceModelReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
46
- logger := log .FromContext (ctx )
47
- loggerDefault := logger .V (logutil .DEFAULT )
48
- loggerDefault .Info ("Reconciling InferenceModel" , "name" , req .NamespacedName )
50
+ if req .Namespace != c .PoolNamespacedName .Namespace {
51
+ return ctrl.Result {}, nil
52
+ }
53
+ logger := log .FromContext (ctx ).V (logutil .DEFAULT ).WithValues ("inferenceModel" , req .Name )
54
+ ctx = ctrl .LoggerInto (ctx , logger )
55
+
56
+ logger .Info ("Reconciling InferenceModel" )
49
57
50
58
infModel := & v1alpha2.InferenceModel {}
59
+ notFound := false
51
60
if err := c .Get (ctx , req .NamespacedName , infModel ); err != nil {
52
- if errors .IsNotFound (err ) {
53
- loggerDefault .Info ("InferenceModel not found. Removing from datastore since object must be deleted" , "name" , req .NamespacedName )
54
- c .Datastore .ModelDelete (infModel .Spec .ModelName )
55
- return ctrl.Result {}, nil
61
+ if ! errors .IsNotFound (err ) {
62
+ logger .Error (err , "Unable to get InferenceModel" )
63
+ return ctrl.Result {}, err
56
64
}
57
- loggerDefault .Error (err , "Unable to get InferenceModel" , "name" , req .NamespacedName )
65
+ notFound = true
66
+ }
67
+
68
+ if notFound || ! infModel .DeletionTimestamp .IsZero () || infModel .Spec .PoolRef .Name != c .PoolNamespacedName .Name {
69
+ // InferenceModel object got deleted or changed the referenced pool.
70
+ err := c .handleModelDeleted (ctx , req .NamespacedName )
58
71
return ctrl.Result {}, err
59
- } else if ! infModel .DeletionTimestamp .IsZero () {
60
- loggerDefault .Info ("InferenceModel is marked for deletion. Removing from datastore" , "name" , req .NamespacedName )
61
- c .Datastore .ModelDelete (infModel .Spec .ModelName )
62
- return ctrl.Result {}, nil
63
72
}
64
73
65
- c .updateDatastore (logger , infModel )
74
+ // Add or update if the InferenceModel instance has a creation timestamp older than the existing entry of the model.
75
+ logger = logger .WithValues ("poolRef" , infModel .Spec .PoolRef ).WithValues ("modelName" , infModel .Spec .ModelName )
76
+ if ! c .Datastore .ModelSetIfOlder (infModel ) {
77
+ logger .Info ("Skipping InferenceModel, existing instance has older creation timestamp" )
78
+
79
+ }
80
+ logger .Info ("Added/Updated InferenceModel" )
81
+
66
82
return ctrl.Result {}, nil
67
83
}
68
84
69
- func (c * InferenceModelReconciler ) updateDatastore (logger logr.Logger , infModel * v1alpha2.InferenceModel ) {
70
- loggerDefault := logger .V (logutil .DEFAULT )
85
+ func (c * InferenceModelReconciler ) handleModelDeleted (ctx context.Context , req types.NamespacedName ) error {
86
+ logger := log .FromContext (ctx )
87
+
88
+ // We will lookup the modelName associated with this object to search for
89
+ // other instance referencing the same ModelName if exist to store the oldest in
90
+ // its place. This ensures that the InferenceModel with the oldest creation
91
+ // timestamp is active.
92
+ existing , exists := c .Datastore .ModelGetByObjName (req )
93
+ if ! exists {
94
+ // No entry exists in the first place, nothing to do.
95
+ return nil
96
+ }
97
+ // Delete the internal object, it may be replaced with another version below.
98
+ c .Datastore .ModelDelete (req )
99
+ logger .Info ("InferenceModel removed from datastore" , "poolRef" , existing .Spec .PoolRef , "modelName" , existing .Spec .ModelName )
71
100
72
- if infModel .Spec .PoolRef .Name == c .PoolNamespacedName .Name {
73
- loggerDefault .Info ("Updating datastore" , "poolRef" , infModel .Spec .PoolRef , "serverPoolName" , c .PoolNamespacedName )
74
- loggerDefault .Info ("Adding/Updating InferenceModel" , "modelName" , infModel .Spec .ModelName )
75
- c .Datastore .ModelSet (infModel )
76
- return
101
+ // List all InferenceModels with a matching ModelName.
102
+ var models v1alpha2.InferenceModelList
103
+ if err := c .List (ctx , & models , client.MatchingFields {modelNameKey : existing .Spec .ModelName }, client .InNamespace (c .PoolNamespacedName .Namespace )); err != nil {
104
+ return fmt .Errorf ("listing models that match the modelName %s: %w" , existing .Spec .ModelName , err )
105
+ }
106
+ if len (models .Items ) == 0 {
107
+ // No other instances of InferenceModels with this ModelName exists.
108
+ return nil
109
+ }
110
+
111
+ var oldest * v1alpha2.InferenceModel
112
+ for i := range models .Items {
113
+ m := & models .Items [i ]
114
+ if m .Spec .ModelName != existing .Spec .ModelName || // The index should filter those out, but just in case!
115
+ m .Spec .PoolRef .Name != c .PoolNamespacedName .Name || // We don't care about other pools, we could setup an index on this too!
116
+ m .Name == existing .Name { // We don't care about the same object, it could be in the list if it was only marked for deletion, but not yet deleted.
117
+ continue
118
+ }
119
+ if oldest == nil || m .ObjectMeta .CreationTimestamp .Before (& oldest .ObjectMeta .CreationTimestamp ) {
120
+ oldest = m
121
+ }
77
122
}
78
- loggerDefault .Info ("Removing/Not adding InferenceModel" , "modelName" , infModel .Spec .ModelName )
79
- // If we get here. The model is not relevant to this pool, remove.
80
- c .Datastore .ModelDelete (infModel .Spec .ModelName )
123
+ if oldest != nil && c .Datastore .ModelSetIfOlder (oldest ) {
124
+ logger .Info ("InferenceModel replaced." ,
125
+ "poolRef" , oldest .Spec .PoolRef ,
126
+ "modelName" , oldest .Spec .ModelName ,
127
+ "newInferenceModel" , types.NamespacedName {Name : oldest .Name , Namespace : oldest .Namespace })
128
+ }
129
+
130
+ return nil
131
+ }
132
+
133
+ func indexInferenceModelsByModelName (obj client.Object ) []string {
134
+ m , ok := obj .(* v1alpha2.InferenceModel )
135
+ if ! ok {
136
+ return nil
137
+ }
138
+ return []string {m .Spec .ModelName }
81
139
}
82
140
83
- func (c * InferenceModelReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
141
+ func (c * InferenceModelReconciler ) SetupWithManager (ctx context.Context , mgr ctrl.Manager ) error {
142
+ // Create an index on ModelName for InferenceModel objects.
143
+ indexer := mgr .GetFieldIndexer ()
144
+ if err := indexer .IndexField (ctx , & v1alpha2.InferenceModel {}, modelNameKey , indexInferenceModelsByModelName ); err != nil {
145
+ return fmt .Errorf ("setting index on ModelName for InferenceModel: %w" , err )
146
+ }
84
147
return ctrl .NewControllerManagedBy (mgr ).
85
148
For (& v1alpha2.InferenceModel {}).
86
149
WithEventFilter (predicate.Funcs {
0 commit comments