Visitar URL original
Production ready feast operator with v1 apiversion · feast-dev/feast@67b4c0a · GitHub
Skip to content

Commit 67b4c0a

Browse files
committed
Production ready feast operator with v1 apiversion
Signed-off-by: jyejare <jyejare@redhat.com>
1 parent 4a9ffae commit 67b4c0a

7 files changed

Lines changed: 17225 additions & 10 deletions

File tree

‎infra/feast-operator/api/v1/featurestore_types.go‎

Lines changed: 682 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2024 Feast Community.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package v1 contains API Schema definitions for the v1 API group
18+
// +kubebuilder:object:generate=true
19+
// +groupName=feast.dev
20+
package v1
21+
22+
import (
23+
"k8s.io/apimachinery/pkg/runtime/schema"
24+
"sigs.k8s.io/controller-runtime/pkg/scheme"
25+
)
26+
27+
var (
28+
// GroupVersion is group version used to register these objects
29+
GroupVersion = schema.GroupVersion{Group: "feast.dev", Version: "v1"}
30+
31+
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
32+
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
33+
34+
// AddToScheme adds the types in this group-version to the given scheme.
35+
AddToScheme = SchemeBuilder.AddToScheme
36+
)

‎infra/feast-operator/api/v1alpha1/featurestore_types.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,7 @@ type ServiceHostnames struct {
657657
// +kubebuilder:resource:shortName=feast
658658
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
659659
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
660+
// +kubebuilder:deprecatedversion:warning="v1alpha1 is deprecated and will be removed in a future release. Please migrate to v1."
660661

661662
// FeatureStore is the Schema for the featurestores API
662663
type FeatureStore struct {

‎infra/feast-operator/cmd/main.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
3838
"sigs.k8s.io/controller-runtime/pkg/webhook"
3939

40+
feastdevv1 "github.com/feast-dev/feast/infra/feast-operator/api/v1"
4041
feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1"
4142
routev1 "github.com/openshift/api/route/v1"
4243

@@ -54,6 +55,7 @@ func init() {
5455
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
5556
utilruntime.Must(routev1.AddToScheme(scheme))
5657
utilruntime.Must(feastdevv1alpha1.AddToScheme(scheme))
58+
utilruntime.Must(feastdevv1.AddToScheme(scheme))
5759
// +kubebuilder:scaffold:scheme
5860
}
5961

‎infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml‎

Lines changed: 8200 additions & 1 deletion
Large diffs are not rendered by default.

‎infra/feast-operator/dist/install.yaml‎

Lines changed: 8200 additions & 1 deletion
Large diffs are not rendered by default.

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

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controller
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122
"reflect"
2223
"time"
2324

@@ -36,6 +37,7 @@ import (
3637
"sigs.k8s.io/controller-runtime/pkg/log"
3738
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3839

40+
feastdevv1 "github.com/feast-dev/feast/infra/feast-operator/api/v1"
3941
feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1"
4042
"github.com/feast-dev/feast/infra/feast-operator/internal/controller/authz"
4143
feasthandler "github.com/feast-dev/feast/infra/feast-operator/internal/controller/handler"
@@ -66,15 +68,49 @@ type FeatureStoreReconciler struct {
6668
// +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;create;update;watch;delete
6769
// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
6870

71+
// convertV1ToV1Alpha1 converts a v1 FeatureStore to v1alpha1 for internal use
72+
// Since both types have identical structures, we use JSON marshaling/unmarshaling
73+
func convertV1ToV1Alpha1(v1Obj *feastdevv1.FeatureStore) *feastdevv1alpha1.FeatureStore {
74+
// Use JSON marshaling/unmarshaling since both types have identical JSON structure
75+
v1alpha1Obj := &feastdevv1alpha1.FeatureStore{
76+
ObjectMeta: v1Obj.ObjectMeta,
77+
}
78+
79+
// Copy spec and status using JSON as intermediate format
80+
specData, err := json.Marshal(v1Obj.Spec)
81+
if err != nil {
82+
// If marshaling fails, return object with just metadata
83+
return v1alpha1Obj
84+
}
85+
if err := json.Unmarshal(specData, &v1alpha1Obj.Spec); err != nil {
86+
// If unmarshaling fails, return object with just metadata
87+
return v1alpha1Obj
88+
}
89+
statusData, err := json.Marshal(v1Obj.Status)
90+
if err != nil {
91+
// If marshaling fails, return object with spec but no status
92+
return v1alpha1Obj
93+
}
94+
if err := json.Unmarshal(statusData, &v1alpha1Obj.Status); err != nil {
95+
// If unmarshaling fails, return object with spec but no status
96+
return v1alpha1Obj
97+
}
98+
99+
return v1alpha1Obj
100+
}
101+
69102
// Reconcile is part of the main kubernetes reconciliation loop which aims to
70103
// move the current state of the cluster closer to the desired state.
71104
// For more details, check Reconcile and its Result here:
72105
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile
73106
func (r *FeatureStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, recErr error) {
74107
logger := log.FromContext(ctx)
75108

76-
cr := &feastdevv1alpha1.FeatureStore{}
77-
err := r.Get(ctx, req.NamespacedName, cr)
109+
// Try to get as v1 first (storage version), then fall back to v1alpha1
110+
var cr *feastdevv1alpha1.FeatureStore
111+
var originalV1Obj *feastdevv1.FeatureStore
112+
v1Obj := &feastdevv1.FeatureStore{}
113+
err := r.Get(ctx, req.NamespacedName, v1Obj)
78114
if err != nil {
79115
if apierrors.IsNotFound(err) {
80116
// CR deleted since request queued, child objects getting GC'd, no requeue
@@ -91,9 +127,33 @@ func (r *FeatureStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request
91127
}
92128
return ctrl.Result{}, nil
93129
}
94-
// error fetching FeatureStore instance, requeue and try again
95-
logger.Error(err, "Unable to get FeatureStore CR")
96-
return ctrl.Result{}, err
130+
// Try v1alpha1 if v1 fails
131+
v1alpha1Obj := &feastdevv1alpha1.FeatureStore{}
132+
err = r.Get(ctx, req.NamespacedName, v1alpha1Obj)
133+
if err != nil {
134+
if apierrors.IsNotFound(err) {
135+
// CR deleted since request queued, child objects getting GC'd, no requeue
136+
logger.V(1).Info("FeatureStore CR not found, has been deleted")
137+
// Clean up namespace registry entry even if the CR is not found
138+
if err := r.cleanupNamespaceRegistry(ctx, &feastdevv1alpha1.FeatureStore{
139+
ObjectMeta: metav1.ObjectMeta{
140+
Name: req.NamespacedName.Name,
141+
Namespace: req.NamespacedName.Namespace,
142+
},
143+
}); err != nil {
144+
logger.Error(err, "Failed to clean up namespace registry entry for deleted FeatureStore")
145+
// Don't return error here as the CR is already deleted
146+
}
147+
return ctrl.Result{}, nil
148+
}
149+
logger.Error(err, "Unable to get FeatureStore CR")
150+
return ctrl.Result{}, err
151+
}
152+
cr = v1alpha1Obj
153+
} else {
154+
// Convert v1 to v1alpha1 for internal use
155+
originalV1Obj = v1Obj
156+
cr = convertV1ToV1Alpha1(v1Obj)
97157
}
98158
currentStatus := cr.Status.DeepCopy()
99159

@@ -109,7 +169,27 @@ func (r *FeatureStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request
109169

110170
result, recErr = r.deployFeast(ctx, cr)
111171
if cr.DeletionTimestamp == nil && !reflect.DeepEqual(currentStatus, cr.Status) {
112-
if err = r.Client.Status().Update(ctx, cr); err != nil {
172+
// Update status - need to update in the original version (v1 if it was v1, v1alpha1 if it was v1alpha1)
173+
var statusObj client.Object
174+
if originalV1Obj != nil {
175+
// Convert back to v1 for status update
176+
originalV1Obj.Status = feastdevv1.FeatureStoreStatus{}
177+
statusData, err := json.Marshal(cr.Status)
178+
if err != nil {
179+
logger.Error(err, "Failed to marshal status for v1 conversion")
180+
statusObj = cr
181+
} else {
182+
if err := json.Unmarshal(statusData, &originalV1Obj.Status); err != nil {
183+
logger.Error(err, "Failed to unmarshal status for v1 conversion")
184+
statusObj = cr
185+
} else {
186+
statusObj = originalV1Obj
187+
}
188+
}
189+
} else {
190+
statusObj = cr
191+
}
192+
if err = r.Client.Status().Update(ctx, statusObj); err != nil {
113193
if apierrors.IsConflict(err) {
114194
logger.Info("FeatureStore object modified, retry syncing status")
115195
// Re-queue and preserve existing recErr
@@ -220,7 +300,7 @@ func (r *FeatureStoreReconciler) deployFeast(ctx context.Context, cr *feastdevv1
220300
// SetupWithManager sets up the controller with the Manager.
221301
func (r *FeatureStoreReconciler) SetupWithManager(mgr ctrl.Manager) error {
222302
bldr := ctrl.NewControllerManagedBy(mgr).
223-
For(&feastdevv1alpha1.FeatureStore{}).
303+
For(&feastdevv1.FeatureStore{}).
224304
Owns(&corev1.ConfigMap{}).
225305
Owns(&appsv1.Deployment{}).
226306
Owns(&corev1.Service{}).
@@ -229,7 +309,12 @@ func (r *FeatureStoreReconciler) SetupWithManager(mgr ctrl.Manager) error {
229309
Owns(&rbacv1.RoleBinding{}).
230310
Owns(&rbacv1.Role{}).
231311
Owns(&batchv1.CronJob{}).
312+
Watches(&feastdevv1.FeatureStore{}, handler.EnqueueRequestsFromMapFunc(r.mapFeastRefsToFeastRequests)).
232313
Watches(&feastdevv1alpha1.FeatureStore{}, handler.EnqueueRequestsFromMapFunc(r.mapFeastRefsToFeastRequests))
314+
315+
// Also watch v1alpha1 for backwards compatibility
316+
bldr = bldr.Watches(&feastdevv1alpha1.FeatureStore{}, &handler.EnqueueRequestForObject{})
317+
233318
if services.IsOpenShift() {
234319
bldr = bldr.Owns(&routev1.Route{})
235320
}
@@ -255,7 +340,18 @@ func (r *FeatureStoreReconciler) cleanupNamespaceRegistry(ctx context.Context, c
255340
// if a remotely referenced FeatureStore is changed, reconcile any FeatureStores that reference it.
256341
func (r *FeatureStoreReconciler) mapFeastRefsToFeastRequests(ctx context.Context, object client.Object) []reconcile.Request {
257342
logger := log.FromContext(ctx)
258-
feastRef := object.(*feastdevv1alpha1.FeatureStore)
343+
344+
// Handle both v1 and v1alpha1 versions
345+
var feastRef *feastdevv1alpha1.FeatureStore
346+
switch obj := object.(type) {
347+
case *feastdevv1.FeatureStore:
348+
feastRef = convertV1ToV1Alpha1(obj)
349+
case *feastdevv1alpha1.FeatureStore:
350+
feastRef = obj
351+
default:
352+
logger.Error(nil, "Unexpected object type in mapFeastRefsToFeastRequests")
353+
return nil
354+
}
259355

260356
// list all FeatureStores in the cluster
261357
var feastList feastdevv1alpha1.FeatureStoreList

0 commit comments

Comments
 (0)