Skip to content

Commit 31ca5de

Browse files
committed
fix: Server-side apply
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
1 parent 24d7025 commit 31ca5de

File tree

5 files changed

+82
-47
lines changed

5 files changed

+82
-47
lines changed

infra/feast-operator/config/rbac/role.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ rules:
110110
- delete
111111
- get
112112
- list
113-
- update
113+
- patch
114114
- watch
115115
- apiGroups:
116116
- policy

infra/feast-operator/dist/install.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20405,7 +20405,7 @@ rules:
2040520405
- delete
2040620406
- get
2040720407
- list
20408-
- update
20408+
- patch
2040920409
- watch
2041020410
- apiGroups:
2041120411
- policy

infra/feast-operator/internal/controller/featurestore_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ type FeatureStoreReconciler struct {
7171
// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
7272
// +kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;patch;delete
7373
// +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete
74-
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;delete
74+
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;patch;delete
7575

7676
// Reconcile is part of the main kubernetes reconciliation loop which aims to
7777
// move the current state of the cluster closer to the desired state.

infra/feast-operator/internal/controller/services/service_monitor.go

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ limitations under the License.
1717
package services
1818

1919
import (
20+
"encoding/json"
21+
22+
feastdevv1 "github.com/feast-dev/feast/infra/feast-operator/api/v1"
2023
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2124
"k8s.io/apimachinery/pkg/runtime/schema"
22-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
25+
"k8s.io/apimachinery/pkg/types"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
2327
"sigs.k8s.io/controller-runtime/pkg/log"
2428
)
2529

@@ -30,32 +34,38 @@ var serviceMonitorGVK = schema.GroupVersionKind{
3034
}
3135

3236
// createOrDeleteServiceMonitor reconciles the ServiceMonitor for the
33-
// FeatureStore's online store metrics endpoint. When the Prometheus Operator
34-
// CRD is not present in the cluster, this is a no-op. When metrics are enabled
35-
// on the online store, a ServiceMonitor is created; otherwise any existing
36-
// ServiceMonitor is deleted.
37+
// FeatureStore's online store metrics endpoint using Server-Side Apply.
38+
// When the Prometheus Operator CRD is not present in the cluster, this is
39+
// a no-op. When metrics are enabled on the online store, a ServiceMonitor
40+
// is applied; otherwise any existing ServiceMonitor is deleted.
3741
func (feast *FeastServices) createOrDeleteServiceMonitor() error {
3842
if !hasServiceMonitorCRD {
3943
return nil
4044
}
4145

4246
if feast.isOnlineStore() && feast.isMetricsEnabled(OnlineFeastType) {
43-
return feast.createServiceMonitor()
47+
return feast.applyServiceMonitor()
4448
}
4549

4650
return feast.deleteServiceMonitor()
4751
}
4852

49-
func (feast *FeastServices) createServiceMonitor() error {
50-
logger := log.FromContext(feast.Handler.Context)
53+
func (feast *FeastServices) applyServiceMonitor() error {
54+
smApply := feast.buildServiceMonitorApplyConfig()
55+
data, err := json.Marshal(smApply)
56+
if err != nil {
57+
return err
58+
}
59+
5160
sm := feast.initServiceMonitor()
52-
if op, err := controllerutil.CreateOrUpdate(feast.Handler.Context, feast.Handler.Client, sm, controllerutil.MutateFn(func() error {
53-
return feast.setServiceMonitor(sm)
54-
})); err != nil {
61+
logger := log.FromContext(feast.Handler.Context)
62+
if err := feast.Handler.Client.Patch(feast.Handler.Context, sm,
63+
client.RawPatch(types.ApplyPatchType, data),
64+
client.FieldOwner(fieldManager), client.ForceOwnership); err != nil {
5565
return err
56-
} else if op == controllerutil.OperationResultCreated || op == controllerutil.OperationResultUpdated {
57-
logger.Info("Successfully reconciled", "ServiceMonitor", sm.GetName(), "operation", op)
5866
}
67+
logger.Info("Successfully applied", "ServiceMonitor", sm.GetName())
68+
5969
return nil
6070
}
6171

@@ -72,25 +82,43 @@ func (feast *FeastServices) initServiceMonitor() *unstructured.Unstructured {
7282
return sm
7383
}
7484

75-
func (feast *FeastServices) setServiceMonitor(sm *unstructured.Unstructured) error {
85+
// buildServiceMonitorApplyConfig constructs the fully desired ServiceMonitor
86+
// state for Server-Side Apply.
87+
func (feast *FeastServices) buildServiceMonitorApplyConfig() map[string]interface{} {
7688
cr := feast.Handler.FeatureStore
77-
78-
sm.SetLabels(feast.getFeastTypeLabels(OnlineFeastType))
79-
80-
sm.Object["spec"] = map[string]interface{}{
81-
"endpoints": []interface{}{
82-
map[string]interface{}{
83-
"port": "metrics",
84-
"path": "/metrics",
89+
objMeta := feast.GetObjectMetaType(OnlineFeastType)
90+
91+
return map[string]interface{}{
92+
"apiVersion": "monitoring.coreos.com/v1",
93+
"kind": "ServiceMonitor",
94+
"metadata": map[string]interface{}{
95+
"name": objMeta.Name,
96+
"namespace": objMeta.Namespace,
97+
"labels": feast.getFeastTypeLabels(OnlineFeastType),
98+
"ownerReferences": []interface{}{
99+
map[string]interface{}{
100+
"apiVersion": feastdevv1.GroupVersion.String(),
101+
"kind": "FeatureStore",
102+
"name": cr.Name,
103+
"uid": string(cr.UID),
104+
"controller": true,
105+
"blockOwnerDeletion": true,
106+
},
85107
},
86108
},
87-
"selector": map[string]interface{}{
88-
"matchLabels": map[string]interface{}{
89-
NameLabelKey: cr.Name,
90-
ServiceTypeLabelKey: string(OnlineFeastType),
109+
"spec": map[string]interface{}{
110+
"endpoints": []interface{}{
111+
map[string]interface{}{
112+
"port": "metrics",
113+
"path": "/metrics",
114+
},
115+
},
116+
"selector": map[string]interface{}{
117+
"matchLabels": map[string]interface{}{
118+
NameLabelKey: cr.Name,
119+
ServiceTypeLabelKey: string(OnlineFeastType),
120+
},
91121
},
92122
},
93123
}
94-
95-
return controllerutil.SetControllerReference(cr, sm, feast.Handler.Scheme)
96124
}

infra/feast-operator/internal/controller/services/service_monitor_test.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -110,35 +110,42 @@ var _ = Describe("ServiceMonitor", func() {
110110
})
111111
})
112112

113-
Describe("setServiceMonitor", func() {
114-
It("should populate the ServiceMonitor spec with correct selector and endpoint", func() {
115-
sm := feast.initServiceMonitor()
116-
Expect(feast.setServiceMonitor(sm)).To(Succeed())
113+
Describe("buildServiceMonitorApplyConfig", func() {
114+
It("should build the correct SSA payload with labels, endpoints, selector, and owner reference", func() {
115+
sm := feast.buildServiceMonitorApplyConfig()
116+
117+
Expect(sm["apiVersion"]).To(Equal("monitoring.coreos.com/v1"))
118+
Expect(sm["kind"]).To(Equal("ServiceMonitor"))
117119

118-
labels := sm.GetLabels()
120+
metadata := sm["metadata"].(map[string]interface{})
121+
Expect(metadata["name"]).To(Equal(feast.GetFeastServiceName(OnlineFeastType)))
122+
Expect(metadata["namespace"]).To(Equal(featureStore.Namespace))
123+
124+
labels := metadata["labels"].(map[string]string)
119125
Expect(labels).To(HaveKeyWithValue(NameLabelKey, featureStore.Name))
120126
Expect(labels).To(HaveKeyWithValue(ServiceTypeLabelKey, string(OnlineFeastType)))
121127

122-
spec, ok := sm.Object["spec"].(map[string]interface{})
123-
Expect(ok).To(BeTrue())
128+
ownerRefs := metadata["ownerReferences"].([]interface{})
129+
Expect(ownerRefs).To(HaveLen(1))
130+
ownerRef := ownerRefs[0].(map[string]interface{})
131+
Expect(ownerRef["apiVersion"]).To(Equal(feastdevv1.GroupVersion.String()))
132+
Expect(ownerRef["kind"]).To(Equal("FeatureStore"))
133+
Expect(ownerRef["name"]).To(Equal(featureStore.Name))
134+
Expect(ownerRef["controller"]).To(BeTrue())
135+
Expect(ownerRef["blockOwnerDeletion"]).To(BeTrue())
124136

125-
endpoints, ok := spec["endpoints"].([]interface{})
126-
Expect(ok).To(BeTrue())
137+
spec := sm["spec"].(map[string]interface{})
138+
139+
endpoints := spec["endpoints"].([]interface{})
127140
Expect(endpoints).To(HaveLen(1))
128141
ep := endpoints[0].(map[string]interface{})
129142
Expect(ep["port"]).To(Equal("metrics"))
130143
Expect(ep["path"]).To(Equal("/metrics"))
131144

132-
selector, ok := spec["selector"].(map[string]interface{})
133-
Expect(ok).To(BeTrue())
145+
selector := spec["selector"].(map[string]interface{})
134146
matchLabels := selector["matchLabels"].(map[string]interface{})
135147
Expect(matchLabels[NameLabelKey]).To(Equal(featureStore.Name))
136148
Expect(matchLabels[ServiceTypeLabelKey]).To(Equal(string(OnlineFeastType)))
137-
138-
ownerRefs := sm.GetOwnerReferences()
139-
Expect(ownerRefs).To(HaveLen(1))
140-
Expect(ownerRefs[0].Name).To(Equal(featureStore.Name))
141-
Expect(*ownerRefs[0].Controller).To(BeTrue())
142149
})
143150
})
144151

0 commit comments

Comments
 (0)