diff --git a/infra/feast-operator/api/v1alpha1/featurestore_types.go b/infra/feast-operator/api/v1alpha1/featurestore_types.go index 0b516630cd..454c909f7d 100644 --- a/infra/feast-operator/api/v1alpha1/featurestore_types.go +++ b/infra/feast-operator/api/v1alpha1/featurestore_types.go @@ -99,7 +99,7 @@ type OfflineStorePersistence struct { // OfflineStoreFilePersistence configures the file-based persistence for the offline store service type OfflineStoreFilePersistence struct { - // +kubebuilder:validation:Enum=dask;duckdb + // +kubebuilder:validation:Enum=file;dask;duckdb Type string `json:"type,omitempty"` PvcConfig *PvcConfig `json:"pvc,omitempty"` } @@ -107,11 +107,12 @@ type OfflineStoreFilePersistence struct { var ValidOfflineStoreFilePersistenceTypes = []string{ "dask", "duckdb", + "file", } // OfflineStoreDBStorePersistence configures the DB store persistence for the offline store service type OfflineStoreDBStorePersistence struct { - // +kubebuilder:validation:Enum=snowflake.offline;bigquery;redshift;spark;postgres;feast_trino.trino.TrinoOfflineStore;redis + // +kubebuilder:validation:Enum=snowflake.offline;bigquery;redshift;spark;postgres;trino;redis;athena;mssql Type string `json:"type"` // Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed. SecretRef corev1.LocalObjectReference `json:"secretRef"` @@ -125,8 +126,10 @@ var ValidOfflineStoreDBStorePersistenceTypes = []string{ "redshift", "spark", "postgres", - "feast_trino.trino.TrinoOfflineStore", + "trino", "redis", + "athena", + "mssql", } // OnlineStore configures the deployed online store service @@ -158,7 +161,7 @@ type OnlineStoreFilePersistence struct { // OnlineStoreDBStorePersistence configures the DB store persistence for the offline store service type OnlineStoreDBStorePersistence struct { - // +kubebuilder:validation:Enum=snowflake.online;redis;ikv;datastore;dynamodb;bigtable;postgres;cassandra;mysql;hazelcast;singlestore + // +kubebuilder:validation:Enum=snowflake.online;redis;ikv;datastore;dynamodb;bigtable;postgres;cassandra;mysql;hazelcast;singlestore;hbase;elasticsearch;qdrant;couchbase Type string `json:"type"` // Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed. SecretRef corev1.LocalObjectReference `json:"secretRef"` @@ -178,6 +181,10 @@ var ValidOnlineStoreDBStorePersistenceTypes = []string{ "mysql", "hazelcast", "singlestore", + "hbase", + "elasticsearch", + "qdrant", + "couchbase", } // LocalRegistryConfig configures the deployed registry service diff --git a/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml b/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml index 6929796d34..61b3e7adcf 100644 --- a/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml +++ b/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml @@ -323,6 +323,7 @@ spec: rule: self.mountPath.matches('^/[^:]*$') type: enum: + - file - dask - duckdb type: string @@ -355,8 +356,10 @@ spec: - redshift - spark - postgres - - feast_trino.trino.TrinoOfflineStore + - trino - redis + - athena + - mssql type: string required: - secretRef @@ -729,6 +732,10 @@ spec: - mysql - hazelcast - singlestore + - hbase + - elasticsearch + - qdrant + - couchbase type: string required: - secretRef @@ -1559,6 +1566,7 @@ spec: rule: self.mountPath.matches('^/[^:]*$') type: enum: + - file - dask - duckdb type: string @@ -1592,8 +1600,10 @@ spec: - redshift - spark - postgres - - feast_trino.trino.TrinoOfflineStore + - trino - redis + - athena + - mssql type: string required: - secretRef @@ -1973,6 +1983,10 @@ spec: - mysql - hazelcast - singlestore + - hbase + - elasticsearch + - qdrant + - couchbase type: string required: - secretRef diff --git a/infra/feast-operator/dist/install.yaml b/infra/feast-operator/dist/install.yaml index 9e213994eb..5d56e78639 100644 --- a/infra/feast-operator/dist/install.yaml +++ b/infra/feast-operator/dist/install.yaml @@ -331,6 +331,7 @@ spec: rule: self.mountPath.matches('^/[^:]*$') type: enum: + - file - dask - duckdb type: string @@ -363,8 +364,10 @@ spec: - redshift - spark - postgres - - feast_trino.trino.TrinoOfflineStore + - trino - redis + - athena + - mssql type: string required: - secretRef @@ -737,6 +740,10 @@ spec: - mysql - hazelcast - singlestore + - hbase + - elasticsearch + - qdrant + - couchbase type: string required: - secretRef @@ -1567,6 +1574,7 @@ spec: rule: self.mountPath.matches('^/[^:]*$') type: enum: + - file - dask - duckdb type: string @@ -1600,8 +1608,10 @@ spec: - redshift - spark - postgres - - feast_trino.trino.TrinoOfflineStore + - trino - redis + - athena + - mssql type: string required: - secretRef @@ -1981,6 +1991,10 @@ spec: - mysql - hazelcast - singlestore + - hbase + - elasticsearch + - qdrant + - couchbase type: string required: - secretRef diff --git a/infra/feast-operator/internal/controller/featurestore_controller_db_store_test.go b/infra/feast-operator/internal/controller/featurestore_controller_db_store_test.go index 60235fe687..377ee6bc51 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_db_store_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_db_store_test.go @@ -78,7 +78,7 @@ sqlalchemy_config_kwargs: pool_pre_ping: true ` -var invalidSecretContainingTypeYamlString = ` +var secretContainingValidTypeYamlString = ` type: cassandra hosts: - 192.168.1.1 @@ -305,37 +305,12 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { Expect(err.Error()).To(Equal("secret key invalid.secret.key doesn't exist in secret online-store-secret")) - By("Referring to a secret that contains parameter named type") - resource = &feastdevv1alpha1.FeatureStore{} - err = k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - secret := &corev1.Secret{} - err = k8sClient.Get(ctx, onlineSecretNamespacedName, secret) - Expect(err).NotTo(HaveOccurred()) - secret.Data[string(services.OnlineDBPersistenceCassandraConfigType)] = []byte(invalidSecretContainingTypeYamlString) - Expect(k8sClient.Update(ctx, secret)).To(Succeed()) - - resource.Spec.Services.OnlineStore.Persistence.DBPersistence.SecretRef = corev1.LocalObjectReference{Name: "online-store-secret"} - resource.Spec.Services.OnlineStore.Persistence.DBPersistence.SecretKeyName = "" - Expect(k8sClient.Update(ctx, resource)).To(Succeed()) - resource = &feastdevv1alpha1.FeatureStore{} - err = k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, - }) - Expect(err).To(HaveOccurred()) - - Expect(err.Error()).To(Equal("secret key cassandra in secret online-store-secret contains invalid tag named type")) - By("Referring to a secret that contains parameter named type with invalid value") resource = &feastdevv1alpha1.FeatureStore{} err = k8sClient.Get(ctx, typeNamespacedName, resource) Expect(err).NotTo(HaveOccurred()) - secret = &corev1.Secret{} + secret := &corev1.Secret{} err = k8sClient.Get(ctx, onlineSecretNamespacedName, secret) Expect(err).NotTo(HaveOccurred()) secret.Data[string(services.OnlineDBPersistenceCassandraConfigType)] = []byte(invalidSecretTypeYamlString) @@ -353,39 +328,7 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { }) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("secret key cassandra in secret online-store-secret contains invalid tag named type")) - - By("Referring to a secret that contains parameter named registry_type") - resource = &feastdevv1alpha1.FeatureStore{} - err = k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - secret = &corev1.Secret{} - err = k8sClient.Get(ctx, onlineSecretNamespacedName, secret) - Expect(err).NotTo(HaveOccurred()) - secret.Data[string(services.OnlineDBPersistenceCassandraConfigType)] = []byte(cassandraYamlString) - Expect(k8sClient.Update(ctx, secret)).To(Succeed()) - - secret = &corev1.Secret{} - err = k8sClient.Get(ctx, registrySecretNamespacedName, secret) - Expect(err).NotTo(HaveOccurred()) - secret.Data["sql_custom_registry_key"] = nil - secret.Data[string(services.RegistryDBPersistenceSQLConfigType)] = []byte(invalidSecretRegistryTypeYamlString) - Expect(k8sClient.Update(ctx, secret)).To(Succeed()) - - resource.Spec.Services.Registry.Local.Persistence.DBPersistence.SecretRef = corev1.LocalObjectReference{Name: "registry-store-secret"} - resource.Spec.Services.Registry.Local.Persistence.DBPersistence.SecretKeyName = "" - Expect(k8sClient.Update(ctx, resource)).To(Succeed()) - resource = &feastdevv1alpha1.FeatureStore{} - err = k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, - }) - Expect(err).To(HaveOccurred()) - - Expect(err.Error()).To(Equal("secret key sql in secret registry-store-secret contains invalid tag named registry_type")) + Expect(err.Error()).To(Equal("secret key cassandra in secret online-store-secret contains tag named type with value wrong")) }) It("should successfully reconcile the resource", func() { @@ -506,6 +449,60 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { Expect(err).NotTo(HaveOccurred()) Expect(controllerutil.HasControllerReference(svc)).To(BeTrue()) Expect(svc.Spec.Ports[0].TargetPort).To(Equal(intstr.FromInt(int(services.FeastServiceConstants[services.RegistryFeastType].TargetHttpPort)))) + + By("Referring to a secret that contains parameter named type") + resource = &feastdevv1alpha1.FeatureStore{} + err = k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + secret := &corev1.Secret{} + err = k8sClient.Get(ctx, onlineSecretNamespacedName, secret) + Expect(err).NotTo(HaveOccurred()) + secret.Data[string(services.OnlineDBPersistenceCassandraConfigType)] = []byte(secretContainingValidTypeYamlString) + Expect(k8sClient.Update(ctx, secret)).To(Succeed()) + + resource.Spec.Services.OnlineStore.Persistence.DBPersistence.SecretRef = corev1.LocalObjectReference{Name: "online-store-secret"} + resource.Spec.Services.OnlineStore.Persistence.DBPersistence.SecretKeyName = "" + Expect(k8sClient.Update(ctx, resource)).To(Succeed()) + resource = &feastdevv1alpha1.FeatureStore{} + err = k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + + Expect(err).To(Not(HaveOccurred())) + + By("Referring to a secret that contains parameter named registry_type") + resource = &feastdevv1alpha1.FeatureStore{} + err = k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + secret = &corev1.Secret{} + err = k8sClient.Get(ctx, onlineSecretNamespacedName, secret) + Expect(err).NotTo(HaveOccurred()) + secret.Data[string(services.OnlineDBPersistenceCassandraConfigType)] = []byte(cassandraYamlString) + Expect(k8sClient.Update(ctx, secret)).To(Succeed()) + + secret = &corev1.Secret{} + err = k8sClient.Get(ctx, registrySecretNamespacedName, secret) + Expect(err).NotTo(HaveOccurred()) + secret.Data["sql_custom_registry_key"] = nil + secret.Data[string(services.RegistryDBPersistenceSQLConfigType)] = []byte(invalidSecretRegistryTypeYamlString) + Expect(k8sClient.Update(ctx, secret)).To(Succeed()) + + resource.Spec.Services.Registry.Local.Persistence.DBPersistence.SecretRef = corev1.LocalObjectReference{Name: "registry-store-secret"} + resource.Spec.Services.Registry.Local.Persistence.DBPersistence.SecretKeyName = "" + Expect(k8sClient.Update(ctx, resource)).To(Succeed()) + resource = &feastdevv1alpha1.FeatureStore{} + err = k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).To(Not(HaveOccurred())) }) It("should properly encode a feature_store.yaml config", func() { diff --git a/infra/feast-operator/internal/controller/services/repo_config.go b/infra/feast-operator/internal/controller/services/repo_config.go index 22052aa724..c70996ab86 100644 --- a/infra/feast-operator/internal/controller/services/repo_config.go +++ b/infra/feast-operator/internal/controller/services/repo_config.go @@ -50,7 +50,7 @@ func (feast *FeastServices) getServiceRepoConfig(feastType FeastServiceType) (Re func getServiceRepoConfig( feastType FeastServiceType, featureStore *feastdevv1alpha1.FeatureStore, - secretExtractionFunc func(secretRef string, secretKeyName string) (map[string]interface{}, error)) (RepoConfig, error) { + secretExtractionFunc func(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error)) (RepoConfig, error) { appliedSpec := featureStore.Status.Applied repoConfig, err := getClientRepoConfig(featureStore, secretExtractionFunc) @@ -59,9 +59,9 @@ func getServiceRepoConfig( } if appliedSpec.AuthzConfig != nil && appliedSpec.AuthzConfig.OidcAuthz != nil { - propertiesMap, err := secretExtractionFunc(appliedSpec.AuthzConfig.OidcAuthz.SecretRef.Name, "") - if err != nil { - return repoConfig, err + propertiesMap, authSecretErr := secretExtractionFunc("", appliedSpec.AuthzConfig.OidcAuthz.SecretRef.Name, "") + if authSecretErr != nil { + return repoConfig, authSecretErr } oidcServerProperties := map[string]interface{}{} @@ -109,7 +109,7 @@ func getServiceRepoConfig( return repoConfig, nil } -func setRepoConfigRegistry(services *feastdevv1alpha1.FeatureStoreServices, secretExtractionFunc func(secretRef string, secretKeyName string) (map[string]interface{}, error), repoConfig *RepoConfig) error { +func setRepoConfigRegistry(services *feastdevv1alpha1.FeatureStoreServices, secretExtractionFunc func(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error), repoConfig *RepoConfig) error { repoConfig.Registry = RegistryConfig{} repoConfig.Registry.Path = DefaultRegistryEphemeralPath registryPersistence := services.Registry.Local.Persistence @@ -129,7 +129,7 @@ func setRepoConfigRegistry(services *feastdevv1alpha1.FeatureStoreServices, secr if len(secretKeyName) == 0 { secretKeyName = string(repoConfig.Registry.RegistryType) } - parametersMap, err := secretExtractionFunc(dbPersistence.SecretRef.Name, secretKeyName) + parametersMap, err := secretExtractionFunc(dbPersistence.Type, dbPersistence.SecretRef.Name, secretKeyName) if err != nil { return err } @@ -149,7 +149,7 @@ func setRepoConfigRegistry(services *feastdevv1alpha1.FeatureStoreServices, secr return nil } -func setRepoConfigOnline(services *feastdevv1alpha1.FeatureStoreServices, secretExtractionFunc func(secretRef string, secretKeyName string) (map[string]interface{}, error), repoConfig *RepoConfig) error { +func setRepoConfigOnline(services *feastdevv1alpha1.FeatureStoreServices, secretExtractionFunc func(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error), repoConfig *RepoConfig) error { repoConfig.OnlineStore = OnlineStoreConfig{} repoConfig.OnlineStore.Path = DefaultOnlineStoreEphemeralPath @@ -170,7 +170,7 @@ func setRepoConfigOnline(services *feastdevv1alpha1.FeatureStoreServices, secret secretKeyName = string(repoConfig.OnlineStore.Type) } - parametersMap, err := secretExtractionFunc(dbPersistence.SecretRef.Name, secretKeyName) + parametersMap, err := secretExtractionFunc(dbPersistence.Type, dbPersistence.SecretRef.Name, secretKeyName) if err != nil { return err } @@ -187,7 +187,7 @@ func setRepoConfigOnline(services *feastdevv1alpha1.FeatureStoreServices, secret return nil } -func setRepoConfigOffline(services *feastdevv1alpha1.FeatureStoreServices, secretExtractionFunc func(secretRef string, secretKeyName string) (map[string]interface{}, error), repoConfig *RepoConfig) error { +func setRepoConfigOffline(services *feastdevv1alpha1.FeatureStoreServices, secretExtractionFunc func(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error), repoConfig *RepoConfig) error { repoConfig.OfflineStore = OfflineStoreConfig{} repoConfig.OfflineStore.Type = OfflineFilePersistenceDaskConfigType offlineStorePersistence := services.OfflineStore.Persistence @@ -205,7 +205,7 @@ func setRepoConfigOffline(services *feastdevv1alpha1.FeatureStoreServices, secre secretKeyName = string(repoConfig.OfflineStore.Type) } - parametersMap, err := secretExtractionFunc(dbPersistence.SecretRef.Name, secretKeyName) + parametersMap, err := secretExtractionFunc(dbPersistence.Type, dbPersistence.SecretRef.Name, secretKeyName) if err != nil { return err } @@ -224,7 +224,7 @@ func setRepoConfigOffline(services *feastdevv1alpha1.FeatureStoreServices, secre return nil } -func (feast *FeastServices) getClientFeatureStoreYaml(secretExtractionFunc func(secretRef string, secretKeyName string) (map[string]interface{}, error)) ([]byte, error) { +func (feast *FeastServices) getClientFeatureStoreYaml(secretExtractionFunc func(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error)) ([]byte, error) { clientRepo, err := getClientRepoConfig(feast.Handler.FeatureStore, secretExtractionFunc) if err != nil { return []byte{}, err @@ -234,7 +234,7 @@ func (feast *FeastServices) getClientFeatureStoreYaml(secretExtractionFunc func( func getClientRepoConfig( featureStore *feastdevv1alpha1.FeatureStore, - secretExtractionFunc func(secretRef string, secretKeyName string) (map[string]interface{}, error)) (RepoConfig, error) { + secretExtractionFunc func(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error)) (RepoConfig, error) { status := featureStore.Status appliedServices := status.Applied.Services clientRepoConfig := RepoConfig{ @@ -292,7 +292,7 @@ func getClientRepoConfig( Type: OidcAuthType, } - propertiesMap, err := secretExtractionFunc(status.Applied.AuthzConfig.OidcAuthz.SecretRef.Name, "") + propertiesMap, err := secretExtractionFunc("", status.Applied.AuthzConfig.OidcAuthz.SecretRef.Name, "") if err != nil { return clientRepoConfig, err } @@ -318,7 +318,7 @@ func getActualPath(filePath string, pvcConfig *feastdevv1alpha1.PvcConfig) strin return path.Join(pvcConfig.MountPath, filePath) } -func (feast *FeastServices) extractConfigFromSecret(secretRef string, secretKeyName string) (map[string]interface{}, error) { +func (feast *FeastServices) extractConfigFromSecret(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error) { secret, err := feast.getSecret(secretRef) if err != nil { return nil, err @@ -330,18 +330,20 @@ func (feast *FeastServices) extractConfigFromSecret(secretRef string, secretKeyN if !exists { return nil, fmt.Errorf("secret key %s doesn't exist in secret %s", secretKeyName, secretRef) } + err = yaml.Unmarshal(val, ¶meters) if err != nil { return nil, fmt.Errorf("secret %s contains invalid value", secretKeyName) } - _, exists = parameters["type"] - if exists { - return nil, fmt.Errorf("secret key %s in secret %s contains invalid tag named type", secretKeyName, secretRef) + + typeVal, typeExists := parameters["type"] + if typeExists && storeType != typeVal { + return nil, fmt.Errorf("secret key %s in secret %s contains tag named type with value %s", secretKeyName, secretRef, typeVal) } - _, exists = parameters["registry_type"] - if exists { - return nil, fmt.Errorf("secret key %s in secret %s contains invalid tag named registry_type", secretKeyName, secretRef) + typeVal, typeExists = parameters["registry_type"] + if typeExists && storeType != typeVal { + return nil, fmt.Errorf("secret key %s in secret %s contains tag named registry_type with value %s", secretKeyName, secretRef, typeVal) } } else { for k, v := range secret.Data { diff --git a/infra/feast-operator/internal/controller/services/repo_config_test.go b/infra/feast-operator/internal/controller/services/repo_config_test.go index b148f90470..7f017f4d10 100644 --- a/infra/feast-operator/internal/controller/services/repo_config_test.go +++ b/infra/feast-operator/internal/controller/services/repo_config_test.go @@ -486,17 +486,17 @@ func minimalFeatureStoreWithAllServices() *feastdevv1alpha1.FeatureStore { return feast } -func emptyMockExtractConfigFromSecret(secretRef string, secretKeyName string) (map[string]interface{}, error) { +func emptyMockExtractConfigFromSecret(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error) { return map[string]interface{}{}, nil } -func mockExtractConfigFromSecret(secretRef string, secretKeyName string) (map[string]interface{}, error) { +func mockExtractConfigFromSecret(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error) { return createParameterMap(), nil } func mockOidcConfigFromSecret( - oidcProperties map[string]interface{}) func(secretRef string, secretKeyName string) (map[string]interface{}, error) { - return func(secretRef string, secretKeyName string) (map[string]interface{}, error) { + oidcProperties map[string]interface{}) func(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error) { + return func(storeType string, secretRef string, secretKeyName string) (map[string]interface{}, error) { return oidcProperties, nil } } diff --git a/infra/feast-operator/test/api/featurestore_types_test.go b/infra/feast-operator/test/api/featurestore_types_test.go index 302abef938..126991266d 100644 --- a/infra/feast-operator/test/api/featurestore_types_test.go +++ b/infra/feast-operator/test/api/featurestore_types_test.go @@ -377,7 +377,7 @@ var _ = Describe("FeatureStore API", func() { }) It("should fail when db persistence type is invalid", func() { - attemptInvalidCreationAndAsserts(ctx, onlineStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: \"snowflake.online\", \"redis\", \"ikv\", \"datastore\", \"dynamodb\", \"bigtable\", \"postgres\", \"cassandra\", \"mysql\", \"hazelcast\", \"singlestore\"") + 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\"") }) }) @@ -388,7 +388,7 @@ var _ = Describe("FeatureStore API", func() { attemptInvalidCreationAndAsserts(ctx, offlineStoreWithUnmanagedFileType(featurestore), "Unsupported value") }) It("should fail when db persistence type is invalid", func() { - attemptInvalidCreationAndAsserts(ctx, offlineStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: \"snowflake.offline\", \"bigquery\", \"redshift\", \"spark\", \"postgres\", \"feast_trino.trino.TrinoOfflineStore\", \"redis\"") + attemptInvalidCreationAndAsserts(ctx, offlineStoreWithDBPersistenceType("invalid", featurestore), "Unsupported value: \"invalid\": supported values: \"snowflake.offline\", \"bigquery\", \"redshift\", \"spark\", \"postgres\", \"trino\", \"redis\", \"athena\", \"mssql\"") }) })