@@ -17,11 +17,16 @@ limitations under the License.
1717package services
1818
1919import (
20+ "encoding/json"
21+
2022 feastdevv1 "github.com/feast-dev/feast/infra/feast-operator/api/v1"
2123 appsv1 "k8s.io/api/apps/v1"
2224 autoscalingv2 "k8s.io/api/autoscaling/v2"
2325 corev1 "k8s.io/api/core/v1"
2426 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+ "k8s.io/apimachinery/pkg/types"
28+ hpaac "k8s.io/client-go/applyconfigurations/autoscaling/v2"
29+ metaac "k8s.io/client-go/applyconfigurations/meta/v1"
2530 "sigs.k8s.io/controller-runtime/pkg/client"
2631 "sigs.k8s.io/controller-runtime/pkg/log"
2732)
@@ -50,8 +55,8 @@ func (feast *FeastServices) getDesiredReplicas() *int32 {
5055}
5156
5257// createOrDeleteHPA reconciles the HorizontalPodAutoscaler for the FeatureStore
53- // deployment using Server-Side Apply. If autoscaling is not configured, any
54- // existing HPA is deleted.
58+ // deployment using Server-Side Apply with typed apply configurations. If
59+ // autoscaling is not configured, any existing HPA is deleted.
5560func (feast * FeastServices ) createOrDeleteHPA () error {
5661 cr := feast .Handler .FeatureStore
5762
@@ -64,84 +69,114 @@ func (feast *FeastServices) createOrDeleteHPA() error {
6469 return feast .Handler .DeleteOwnedFeastObj (hpa )
6570 }
6671
67- hpa := feast .buildHPA ()
72+ hpaAC := feast .buildHPAApplyConfig ()
73+ data , err := json .Marshal (hpaAC )
74+ if err != nil {
75+ return err
76+ }
77+
78+ hpa := & autoscalingv2.HorizontalPodAutoscaler {ObjectMeta : feast .GetObjectMeta ()}
6879 logger := log .FromContext (feast .Handler .Context )
6980 if err := feast .Handler .Client .Patch (feast .Handler .Context , hpa ,
70- client .Apply , client .FieldOwner (fieldManager ), client .ForceOwnership ); err != nil {
81+ client .RawPatch (types .ApplyPatchType , data ),
82+ client .FieldOwner (fieldManager ), client .ForceOwnership ); err != nil {
7183 return err
7284 }
7385 logger .Info ("Successfully applied" , "HorizontalPodAutoscaler" , hpa .Name )
7486
7587 return nil
7688}
7789
78- // buildHPA constructs the fully desired HPA state for Server-Side Apply.
79- func (feast * FeastServices ) buildHPA () * autoscalingv2.HorizontalPodAutoscaler {
90+ // buildHPAApplyConfig constructs the fully desired HPA state as a typed apply
91+ // configuration for Server-Side Apply.
92+ func (feast * FeastServices ) buildHPAApplyConfig () * hpaac.HorizontalPodAutoscalerApplyConfiguration {
8093 cr := feast .Handler .FeatureStore
8194 autoscaling := cr .Status .Applied .Services .Scaling .Autoscaling
82-
95+ objMeta := feast . GetObjectMeta ()
8396 deploy := feast .initFeastDeploy ()
97+
8498 minReplicas := defaultHPAMinReplicas
8599 if autoscaling .MinReplicas != nil {
86100 minReplicas = * autoscaling .MinReplicas
87101 }
88102
89- metrics := defaultHPAMetrics ()
103+ hpa := hpaac .HorizontalPodAutoscaler (objMeta .Name , objMeta .Namespace ).
104+ WithLabels (feast .getLabels ()).
105+ WithOwnerReferences (
106+ metaac .OwnerReference ().
107+ WithAPIVersion (feastdevv1 .GroupVersion .String ()).
108+ WithKind ("FeatureStore" ).
109+ WithName (cr .Name ).
110+ WithUID (cr .UID ).
111+ WithController (true ).
112+ WithBlockOwnerDeletion (true ),
113+ ).
114+ WithSpec (hpaac .HorizontalPodAutoscalerSpec ().
115+ WithScaleTargetRef (
116+ hpaac .CrossVersionObjectReference ().
117+ WithAPIVersion (appsv1 .SchemeGroupVersion .String ()).
118+ WithKind ("Deployment" ).
119+ WithName (deploy .Name ),
120+ ).
121+ WithMinReplicas (minReplicas ).
122+ WithMaxReplicas (autoscaling .MaxReplicas ),
123+ )
124+
90125 if len (autoscaling .Metrics ) > 0 {
91- metrics = autoscaling .Metrics
92- }
93-
94- isController := true
95- hpa := & autoscalingv2.HorizontalPodAutoscaler {
96- TypeMeta : metav1.TypeMeta {
97- APIVersion : autoscalingv2 .SchemeGroupVersion .String (),
98- Kind : "HorizontalPodAutoscaler" ,
99- },
100- ObjectMeta : metav1.ObjectMeta {
101- Name : feast .GetObjectMeta ().Name ,
102- Namespace : feast .GetObjectMeta ().Namespace ,
103- Labels : feast .getLabels (),
104- OwnerReferences : []metav1.OwnerReference {
105- {
106- APIVersion : feastdevv1 .GroupVersion .String (),
107- Kind : "FeatureStore" ,
108- Name : cr .Name ,
109- UID : cr .UID ,
110- Controller : & isController ,
111- BlockOwnerDeletion : & isController ,
112- },
113- },
114- },
115- Spec : autoscalingv2.HorizontalPodAutoscalerSpec {
116- ScaleTargetRef : autoscalingv2.CrossVersionObjectReference {
117- APIVersion : appsv1 .SchemeGroupVersion .String (),
118- Kind : "Deployment" ,
119- Name : deploy .Name ,
120- },
121- MinReplicas : & minReplicas ,
122- MaxReplicas : autoscaling .MaxReplicas ,
123- Metrics : metrics ,
124- Behavior : autoscaling .Behavior ,
125- },
126+ hpa .Spec .Metrics = convertMetrics (autoscaling .Metrics )
127+ } else {
128+ hpa .Spec .Metrics = defaultHPAMetrics ()
129+ }
130+
131+ if autoscaling .Behavior != nil {
132+ hpa .Spec .Behavior = convertBehavior (autoscaling .Behavior )
126133 }
127134
128135 return hpa
129136}
130137
131- func defaultHPAMetrics () []autoscalingv2.MetricSpec {
132- utilization := defaultHPACPUUtilization
133- return []autoscalingv2.MetricSpec {
134- {
135- Type : autoscalingv2 .ResourceMetricSourceType ,
136- Resource : & autoscalingv2.ResourceMetricSource {
137- Name : corev1 .ResourceCPU ,
138- Target : autoscalingv2.MetricTarget {
139- Type : autoscalingv2 .UtilizationMetricType ,
140- AverageUtilization : & utilization ,
141- },
142- },
143- },
138+ func defaultHPAMetrics () []hpaac.MetricSpecApplyConfiguration {
139+ return []hpaac.MetricSpecApplyConfiguration {
140+ * hpaac .MetricSpec ().
141+ WithType (autoscalingv2 .ResourceMetricSourceType ).
142+ WithResource (
143+ hpaac .ResourceMetricSource ().
144+ WithName (corev1 .ResourceCPU ).
145+ WithTarget (
146+ hpaac .MetricTarget ().
147+ WithType (autoscalingv2 .UtilizationMetricType ).
148+ WithAverageUtilization (defaultHPACPUUtilization ),
149+ ),
150+ ),
151+ }
152+ }
153+
154+ // convertMetrics converts standard API metric specs to their apply configuration
155+ // equivalents via JSON round-trip (the types share identical JSON schemas).
156+ func convertMetrics (metrics []autoscalingv2.MetricSpec ) []hpaac.MetricSpecApplyConfiguration {
157+ data , err := json .Marshal (metrics )
158+ if err != nil {
159+ return nil
160+ }
161+ var result []hpaac.MetricSpecApplyConfiguration
162+ if err := json .Unmarshal (data , & result ); err != nil {
163+ return nil
164+ }
165+ return result
166+ }
167+
168+ // convertBehavior converts a standard API behavior spec to its apply configuration
169+ // equivalent via JSON round-trip.
170+ func convertBehavior (behavior * autoscalingv2.HorizontalPodAutoscalerBehavior ) * hpaac.HorizontalPodAutoscalerBehaviorApplyConfiguration {
171+ data , err := json .Marshal (behavior )
172+ if err != nil {
173+ return nil
174+ }
175+ result := & hpaac.HorizontalPodAutoscalerBehaviorApplyConfiguration {}
176+ if err := json .Unmarshal (data , result ); err != nil {
177+ return nil
144178 }
179+ return result
145180}
146181
147182// updateScalingStatus updates the scaling status fields using the deployment
0 commit comments