Skip to content

Commit d937dcb

Browse files
authored
chore: Operator / Feast should have matching Data Source types (#5041)
data store types Signed-off-by: Tommy Hughes <[email protected]>
1 parent b24d531 commit d937dcb

File tree

11 files changed

+145
-19
lines changed

11 files changed

+145
-19
lines changed

.github/workflows/operator_pr.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ jobs:
1111
with:
1212
go-version: 1.22.9
1313
- name: Operator tests
14-
run: |
15-
cd infra/feast-operator/
16-
make test
14+
run: make -C infra/feast-operator test
1715
- name: After code formatting, check for uncommitted differences
1816
run: git diff --exit-code infra/feast-operator

.github/workflows/pr_local_integration_tests.yml

+6
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,9 @@ jobs:
5656
- name: Test local integration tests
5757
if: ${{ always() }} # this will guarantee that step won't be canceled and resources won't leak
5858
run: make test-python-integration-local
59+
- name: Install Go
60+
uses: actions/setup-go@v5
61+
with:
62+
go-version: 1.22.9
63+
- name: Operator Data Source types test
64+
run: make -C infra/feast-operator test-datasources

infra/feast-operator/.golangci.yml

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ issues:
1616
linters:
1717
- dupl
1818
- lll
19+
- path: "test/*"
20+
linters:
21+
- lll
1922
linters:
2023
disable-all: true
2124
enable:

infra/feast-operator/Makefile

+7-1
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,19 @@ vet: ## Run go vet against code.
113113

114114
.PHONY: test
115115
test: build-installer vet lint envtest ## Run tests.
116-
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
116+
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v test/e2e | grep -v test/data-source-types) -coverprofile cover.out
117117

118118
# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
119119
.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up.
120120
test-e2e:
121121
go test -timeout 30m ./test/e2e/ -v -ginkgo.v
122122

123+
# Requires python3
124+
.PHONY: test-datasources
125+
test-datasources:
126+
python3 test/data-source-types/data-source-types.py
127+
go test ./test/data-source-types/
128+
123129
.PHONY: lint
124130
lint: golangci-lint ## Run golangci-lint linter & yamllint
125131
$(GOLANGCI_LINT) run

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

+5-6
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ var ValidOfflineStoreFilePersistenceTypes = []string{
114114

115115
// OfflineStoreDBStorePersistence configures the DB store persistence for the offline store service
116116
type OfflineStoreDBStorePersistence struct {
117-
// Type of the persistence type you want to use. Allowed values are: snowflake.offline, bigquery, redshift, spark, postgres, trino, redis, athena, mssql
118-
// +kubebuilder:validation:Enum=snowflake.offline;bigquery;redshift;spark;postgres;trino;redis;athena;mssql
117+
// Type of the persistence type you want to use.
118+
// +kubebuilder:validation:Enum=snowflake.offline;bigquery;redshift;spark;postgres;trino;athena;mssql
119119
Type string `json:"type"`
120120
// Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed.
121121
SecretRef corev1.LocalObjectReference `json:"secretRef"`
@@ -130,7 +130,6 @@ var ValidOfflineStoreDBStorePersistenceTypes = []string{
130130
"spark",
131131
"postgres",
132132
"trino",
133-
"redis",
134133
"athena",
135134
"mssql",
136135
}
@@ -160,7 +159,7 @@ type OnlineStoreFilePersistence struct {
160159

161160
// OnlineStoreDBStorePersistence configures the DB store persistence for the online store service
162161
type OnlineStoreDBStorePersistence struct {
163-
// Type of the persistence type you want to use. Allowed values are: snowflake.online, redis, ikv, datastore, dynamodb, bigtable, postgres, cassandra, mysql, hazelcast, singlestore, hbase, elasticsearch, qdrant, couchbase, milvus
162+
// Type of the persistence type you want to use.
164163
// +kubebuilder:validation:Enum=snowflake.online;redis;ikv;datastore;dynamodb;bigtable;postgres;cassandra;mysql;hazelcast;singlestore;hbase;elasticsearch;qdrant;couchbase;milvus
165164
Type string `json:"type"`
166165
// Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed.
@@ -215,7 +214,7 @@ type RegistryFilePersistence struct {
215214

216215
// RegistryDBStorePersistence configures the DB store persistence for the registry service
217216
type RegistryDBStorePersistence struct {
218-
// Type of the persistence type you want to use. Allowed values are: sql, snowflake.registry
217+
// Type of the persistence type you want to use.
219218
// +kubebuilder:validation:Enum=sql;snowflake.registry
220219
Type string `json:"type"`
221220
// Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed.
@@ -225,8 +224,8 @@ type RegistryDBStorePersistence struct {
225224
}
226225

227226
var ValidRegistryDBStorePersistenceTypes = []string{
228-
"snowflake.registry",
229227
"sql",
228+
"snowflake.registry",
230229
}
231230

232231
// PvcConfig defines the settings for a persistent file store based on PVCs.

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

-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ spec:
170170
- spark
171171
- postgres
172172
- trino
173-
- redis
174173
- athena
175174
- mssql
176175
type: string
@@ -2083,7 +2082,6 @@ spec:
20832082
- spark
20842083
- postgres
20852084
- trino
2086-
- redis
20872085
- athena
20882086
- mssql
20892087
type: string

infra/feast-operator/dist/install.yaml

-2
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ spec:
178178
- spark
179179
- postgres
180180
- trino
181-
- redis
182181
- athena
183182
- mssql
184183
type: string
@@ -2091,7 +2090,6 @@ spec:
20912090
- spark
20922091
- postgres
20932092
- trino
2094-
- redis
20952093
- athena
20962094
- mssql
20972095
type: string

infra/feast-operator/docs/api/markdown/ref.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ _Appears in:_
210210

211211
| Field | Description |
212212
| --- | --- |
213-
| `type` _string_ | Type of the persistence type you want to use. Allowed values are: snowflake.offline, bigquery, redshift, spark, postgres, trino, redis, athena, mssql |
213+
| `type` _string_ | Type of the persistence type you want to use. |
214214
| `secretRef` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core)_ | Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed. |
215215
| `secretKeyName` _string_ | By default, the selected store "type" is used as the SecretKeyName |
216216

@@ -286,7 +286,7 @@ _Appears in:_
286286

287287
| Field | Description |
288288
| --- | --- |
289-
| `type` _string_ | Type of the persistence type you want to use. Allowed values are: snowflake.online, redis, ikv, datastore, dynamodb, bigtable, postgres, cassandra, mysql, hazelcast, singlestore, hbase, elasticsearch, qdrant, couchbase, milvus |
289+
| `type` _string_ | Type of the persistence type you want to use. |
290290
| `secretRef` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core)_ | Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed. |
291291
| `secretKeyName` _string_ | By default, the selected store "type" is used as the SecretKeyName |
292292

infra/feast-operator/test/api/featurestore_types_test.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package api
22

33
import (
44
"context"
5+
"fmt"
6+
"strings"
57

68
. "github.com/onsi/ginkgo/v2"
79
. "github.com/onsi/gomega"
@@ -334,6 +336,16 @@ func registryStoreWithDBPersistenceType(dbPersistenceType string, featureStore *
334336
return fsCopy
335337
}
336338

339+
func quotedSlice(stringSlice []string) string {
340+
quotedSlice := make([]string, len(stringSlice))
341+
342+
for i, str := range stringSlice {
343+
quotedSlice[i] = fmt.Sprintf("\"%s\"", str)
344+
}
345+
346+
return strings.Join(quotedSlice, ", ")
347+
}
348+
337349
const resourceName = "test-resource"
338350
const namespaceName = "default"
339351

@@ -377,7 +389,7 @@ var _ = Describe("FeatureStore API", func() {
377389
})
378390

379391
It("should fail when db persistence type is invalid", func() {
380-
attemptInvalidCreationAndAsserts(ctx, onlineStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: \"snowflake.online\", \"redis\", \"ikv\", \"datastore\", \"dynamodb\", \"bigtable\", \"postgres\", \"cassandra\", \"mysql\", \"hazelcast\", \"singlestore\", \"hbase\", \"elasticsearch\", \"qdrant\", \"couchbase\"")
392+
attemptInvalidCreationAndAsserts(ctx, onlineStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: "+quotedSlice(feastdevv1alpha1.ValidOnlineStoreDBStorePersistenceTypes))
381393
})
382394
})
383395

@@ -388,7 +400,7 @@ var _ = Describe("FeatureStore API", func() {
388400
attemptInvalidCreationAndAsserts(ctx, offlineStoreWithUnmanagedFileType(featurestore), "Unsupported value")
389401
})
390402
It("should fail when db persistence type is invalid", func() {
391-
attemptInvalidCreationAndAsserts(ctx, offlineStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: \"snowflake.offline\", \"bigquery\", \"redshift\", \"spark\", \"postgres\", \"trino\", \"redis\", \"athena\", \"mssql\"")
403+
attemptInvalidCreationAndAsserts(ctx, offlineStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: "+quotedSlice(feastdevv1alpha1.ValidOfflineStoreDBStorePersistenceTypes))
392404
})
393405
})
394406

@@ -410,7 +422,7 @@ var _ = Describe("FeatureStore API", func() {
410422
attemptInvalidCreationAndAsserts(ctx, registryWithS3AdditionalKeywordsForGsBucket(featurestore), "Additional S3 settings are available only for S3 object store URIs")
411423
})
412424
It("should fail when db persistence type is invalid", func() {
413-
attemptInvalidCreationAndAsserts(ctx, registryStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: \"sql\", \"snowflake.registry\"")
425+
attemptInvalidCreationAndAsserts(ctx, registryStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: "+quotedSlice(feastdevv1alpha1.ValidRegistryDBStorePersistenceTypes))
414426
})
415427
})
416428

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import os
2+
from feast.repo_config import REGISTRY_CLASS_FOR_TYPE, OFFLINE_STORE_CLASS_FOR_TYPE, ONLINE_STORE_CLASS_FOR_TYPE, LEGACY_ONLINE_STORE_CLASS_FOR_TYPE
3+
4+
def save_in_script_directory(filename: str, typedict: dict[str, str]):
5+
script_dir = os.path.dirname(os.path.abspath(__file__))
6+
file_path = os.path.join(script_dir, filename)
7+
8+
with open(file_path, 'w') as file:
9+
for k in typedict.keys():
10+
file.write(k+"\n")
11+
12+
for legacyType in LEGACY_ONLINE_STORE_CLASS_FOR_TYPE.keys():
13+
if legacyType in ONLINE_STORE_CLASS_FOR_TYPE:
14+
del ONLINE_STORE_CLASS_FOR_TYPE[legacyType]
15+
16+
save_in_script_directory("registry.out", REGISTRY_CLASS_FOR_TYPE)
17+
save_in_script_directory("online-store.out", ONLINE_STORE_CLASS_FOR_TYPE)
18+
save_in_script_directory("offline-store.out", OFFLINE_STORE_CLASS_FOR_TYPE)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package datasources
2+
3+
import (
4+
"bufio"
5+
"os"
6+
"slices"
7+
"testing"
8+
9+
. "github.com/onsi/ginkgo/v2"
10+
. "github.com/onsi/gomega"
11+
12+
feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1"
13+
"github.com/feast-dev/feast/infra/feast-operator/internal/controller/services"
14+
)
15+
16+
func TestDataSourceTypes(t *testing.T) {
17+
RegisterFailHandler(Fail)
18+
19+
RunSpecs(t, "Data Source Suite")
20+
}
21+
22+
var _ = Describe("FeatureStore Data Source Types", func() {
23+
Context("When checking against the python code in feast.repo_config", func() {
24+
It("should match defined registry persistence types in the operator", func() {
25+
registryFilePersistenceTypes := []string{string(services.RegistryFileConfigType)}
26+
registryPersistenceTypes := append(feastdevv1alpha1.ValidRegistryDBStorePersistenceTypes, registryFilePersistenceTypes...)
27+
checkPythonPersistenceTypes("registry.out", registryPersistenceTypes)
28+
})
29+
It("should match defined onlineStore persistence types in the operator", func() {
30+
onlineFilePersistenceTypes := []string{string(services.OnlineSqliteConfigType)}
31+
onlinePersistenceTypes := append(feastdevv1alpha1.ValidOnlineStoreDBStorePersistenceTypes, onlineFilePersistenceTypes...)
32+
checkPythonPersistenceTypes("online-store.out", onlinePersistenceTypes)
33+
})
34+
It("should match defined offlineStore persistence types in the operator", func() {
35+
offlinePersistenceTypes := append(feastdevv1alpha1.ValidOfflineStoreDBStorePersistenceTypes, feastdevv1alpha1.ValidOfflineStoreFilePersistenceTypes...)
36+
checkPythonPersistenceTypes("offline-store.out", offlinePersistenceTypes)
37+
})
38+
})
39+
})
40+
41+
func checkPythonPersistenceTypes(fileName string, operatorDsTypes []string) {
42+
feastDsTypes, err := readFileLines(fileName)
43+
Expect(err).NotTo(HaveOccurred())
44+
45+
// Add remote type to slice, as its not a file or db type and we want to limit its use to registry service when deploying with the operator
46+
operatorDsTypes = append(operatorDsTypes, "remote")
47+
missingFeastTypes := []string{}
48+
for _, ods := range operatorDsTypes {
49+
if len(ods) > 0 {
50+
if !slices.Contains(feastDsTypes, ods) {
51+
missingFeastTypes = append(missingFeastTypes, ods)
52+
}
53+
}
54+
}
55+
Expect(missingFeastTypes).To(BeEmpty())
56+
57+
missingOperatorTypes := []string{}
58+
for _, fds := range feastDsTypes {
59+
if len(fds) > 0 {
60+
if !slices.Contains(operatorDsTypes, fds) {
61+
missingOperatorTypes = append(missingOperatorTypes, fds)
62+
}
63+
}
64+
}
65+
Expect(missingOperatorTypes).To(BeEmpty())
66+
}
67+
68+
func readFileLines(filePath string) ([]string, error) {
69+
file, err := os.Open(filePath)
70+
Expect(err).NotTo(HaveOccurred())
71+
defer closeFile(file)
72+
73+
var lines []string
74+
scanner := bufio.NewScanner(file)
75+
for scanner.Scan() {
76+
lines = append(lines, scanner.Text())
77+
}
78+
79+
err = scanner.Err()
80+
Expect(err).NotTo(HaveOccurred())
81+
82+
return lines, nil
83+
}
84+
85+
func closeFile(file *os.File) {
86+
err := file.Close()
87+
Expect(err).NotTo(HaveOccurred())
88+
}

0 commit comments

Comments
 (0)