From 6a27e6b52389c01db8aa72ae91e1079ef0f77c2b Mon Sep 17 00:00:00 2001 From: Nikolaus Schuetz Date: Mon, 20 Jul 2026 11:11:16 -0700 Subject: [PATCH] fix(operator): add spec.services.onlineStore.disabled to opt out of the online store The operator always deploys an online store, forcing a serving pod and persistence onto registry-only or offline-only FeatureStores (#6586). Add a `disabled` field on the online store spec to opt those deployments out explicitly. Deployment is gated through isOnlineStore(), so disabling also skips the serving pod, metrics, and service monitor. The default is unchanged -- omitting the online store block still deploys it with defaults -- so this is not a breaking change on upgrade. Signed-off-by: Nikolaus Schuetz --- .secrets.baseline | 4 +- .../api/v1/featurestore_types.go | 5 ++ .../crd/bases/feast.dev_featurestores.yaml | 10 +++ infra/feast-operator/dist/install.yaml | 10 +++ infra/feast-operator/docs/api/markdown/ref.md | 3 + .../internal/controller/services/services.go | 2 +- .../internal/controller/services/util.go | 34 ++++---- .../internal/controller/services/util_test.go | 83 +++++++++++++++++++ 8 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 infra/feast-operator/internal/controller/services/util_test.go diff --git a/.secrets.baseline b/.secrets.baseline index 0eefabade70..cc84f3f96b7 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -957,7 +957,7 @@ "filename": "infra/feast-operator/api/v1/featurestore_types.go", "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", "is_verified": false, - "line_number": 936 + "line_number": 941 } ], "infra/feast-operator/api/v1/zz_generated.deepcopy.go": [ @@ -1555,5 +1555,5 @@ } ] }, - "generated_at": "2026-06-26T06:19:05Z" + "generated_at": "2026-07-20T18:19:30Z" } diff --git a/infra/feast-operator/api/v1/featurestore_types.go b/infra/feast-operator/api/v1/featurestore_types.go index f0331ecb1b4..bbee3ed204b 100644 --- a/infra/feast-operator/api/v1/featurestore_types.go +++ b/infra/feast-operator/api/v1/featurestore_types.go @@ -544,6 +544,11 @@ type OnlineStore struct { // Controls metrics granularity, offline push batching, and MCP. // +optional Serving *ServingConfig `json:"serving,omitempty"` + // Disabled skips deploying the online store service entirely, including its + // serving pod and persistence. Omitting the online store block, or setting + // this to false, deploys the online store with defaults as before. + // +optional + Disabled bool `json:"disabled,omitempty"` } // ServingConfig configures the feature_server section of the generated feature_store.yaml. 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 11650995e04..b8354fe618a 100644 --- a/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml +++ b/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml @@ -2258,6 +2258,11 @@ spec: onlineStore: description: OnlineStore configures the online store service properties: + disabled: + description: |- + Disabled skips deploying the online store service entirely, including its + serving pod and persistence. + type: boolean persistence: description: OnlineStorePersistence configures the persistence settings for the online store service @@ -8561,6 +8566,11 @@ spec: onlineStore: description: OnlineStore configures the online store service properties: + disabled: + description: |- + Disabled skips deploying the online store service entirely, including its + serving pod and persistence. + type: boolean persistence: description: OnlineStorePersistence configures the persistence settings for the online store service diff --git a/infra/feast-operator/dist/install.yaml b/infra/feast-operator/dist/install.yaml index 57853e4b83f..b9c1ae30cc6 100644 --- a/infra/feast-operator/dist/install.yaml +++ b/infra/feast-operator/dist/install.yaml @@ -2266,6 +2266,11 @@ spec: onlineStore: description: OnlineStore configures the online store service properties: + disabled: + description: |- + Disabled skips deploying the online store service entirely, including its + serving pod and persistence. + type: boolean persistence: description: OnlineStorePersistence configures the persistence settings for the online store service @@ -8569,6 +8574,11 @@ spec: onlineStore: description: OnlineStore configures the online store service properties: + disabled: + description: |- + Disabled skips deploying the online store service entirely, including its + serving pod and persistence. + type: boolean persistence: description: OnlineStorePersistence configures the persistence settings for the online store service diff --git a/infra/feast-operator/docs/api/markdown/ref.md b/infra/feast-operator/docs/api/markdown/ref.md index 0a7782feb2a..27dcf56a307 100644 --- a/infra/feast-operator/docs/api/markdown/ref.md +++ b/infra/feast-operator/docs/api/markdown/ref.md @@ -654,6 +654,9 @@ _Appears in:_ | `persistence` _[OnlineStorePersistence](#onlinestorepersistence)_ | | | `serving` _[ServingConfig](#servingconfig)_ | Serving configures the Feast feature_server section written into feature_store.yaml for the online serve pod. Controls metrics granularity, offline push batching, and MCP. | +| `disabled` _boolean_ | Disabled skips deploying the online store service entirely, including its +serving pod and persistence. Omitting the online store block, or setting +this to false, deploys the online store with defaults as before. | #### OnlineStoreDBStorePersistence diff --git a/infra/feast-operator/internal/controller/services/services.go b/infra/feast-operator/internal/controller/services/services.go index c6271c28d3a..0290ba2a0b0 100644 --- a/infra/feast-operator/internal/controller/services/services.go +++ b/infra/feast-operator/internal/controller/services/services.go @@ -1274,7 +1274,7 @@ func (feast *FeastServices) isOnlineServer() bool { func (feast *FeastServices) isOnlineStore() bool { appliedServices := feast.Handler.FeatureStore.Status.Applied.Services - return appliedServices != nil && appliedServices.OnlineStore != nil + return appliedServices != nil && appliedServices.OnlineStore != nil && !appliedServices.OnlineStore.Disabled } func (feast *FeastServices) noLocalCoreServerConfigured() bool { diff --git a/infra/feast-operator/internal/controller/services/util.go b/infra/feast-operator/internal/controller/services/util.go index 387ccdb631b..7b04a75183d 100644 --- a/infra/feast-operator/internal/controller/services/util.go +++ b/infra/feast-operator/internal/controller/services/util.go @@ -163,30 +163,32 @@ func ApplyDefaultsToStatus(cr *feastdevv1.FeatureStore) { } } - // default to onlineStore service deployment + // default to onlineStore service deployment unless it is explicitly disabled if services.OnlineStore == nil { services.OnlineStore = &feastdevv1.OnlineStore{} } - if services.OnlineStore.Persistence == nil { - services.OnlineStore.Persistence = &feastdevv1.OnlineStorePersistence{} - } - - if services.OnlineStore.Persistence.DBPersistence == nil { - if services.OnlineStore.Persistence.FilePersistence == nil { - services.OnlineStore.Persistence.FilePersistence = &feastdevv1.OnlineStoreFilePersistence{} + if !services.OnlineStore.Disabled { + if services.OnlineStore.Persistence == nil { + services.OnlineStore.Persistence = &feastdevv1.OnlineStorePersistence{} } - if len(services.OnlineStore.Persistence.FilePersistence.Path) == 0 { - services.OnlineStore.Persistence.FilePersistence.Path = defaultOnlineStorePath(cr) - } + if services.OnlineStore.Persistence.DBPersistence == nil { + if services.OnlineStore.Persistence.FilePersistence == nil { + services.OnlineStore.Persistence.FilePersistence = &feastdevv1.OnlineStoreFilePersistence{} + } - ensurePVCDefaults(services.OnlineStore.Persistence.FilePersistence.PvcConfig, OnlineFeastType) - } + if len(services.OnlineStore.Persistence.FilePersistence.Path) == 0 { + services.OnlineStore.Persistence.FilePersistence.Path = defaultOnlineStorePath(cr) + } + + ensurePVCDefaults(services.OnlineStore.Persistence.FilePersistence.PvcConfig, OnlineFeastType) + } - if services.OnlineStore.Server == nil { - services.OnlineStore.Server = &feastdevv1.ServerConfigs{} + if services.OnlineStore.Server == nil { + services.OnlineStore.Server = &feastdevv1.ServerConfigs{} + } + setDefaultCtrConfigs(&services.OnlineStore.Server.ContainerConfigs.DefaultCtrConfigs) } - setDefaultCtrConfigs(&services.OnlineStore.Server.ContainerConfigs.DefaultCtrConfigs) if services.UI != nil { setDefaultCtrConfigs(&services.UI.ContainerConfigs.DefaultCtrConfigs) diff --git a/infra/feast-operator/internal/controller/services/util_test.go b/infra/feast-operator/internal/controller/services/util_test.go new file mode 100644 index 00000000000..89c3fa55a72 --- /dev/null +++ b/infra/feast-operator/internal/controller/services/util_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2024 Feast Community. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package services + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + feastdevv1 "github.com/feast-dev/feast/infra/feast-operator/api/v1" +) + +var _ = Describe("ApplyDefaultsToStatus", func() { + It("deploys the online store with defaults when it is not declared", func() { + cr := &feastdevv1.FeatureStore{ + Spec: feastdevv1.FeatureStoreSpec{ + FeastProject: "test_project", + Services: &feastdevv1.FeatureStoreServices{}, + }, + } + + ApplyDefaultsToStatus(cr) + + online := cr.Status.Applied.Services.OnlineStore + Expect(online).ToNot(BeNil()) + Expect(online.Disabled).To(BeFalse()) + Expect(online.Persistence).ToNot(BeNil()) + Expect(online.Server).ToNot(BeNil()) + }) + + It("applies online store defaults when it is declared", func() { + cr := &feastdevv1.FeatureStore{ + Spec: feastdevv1.FeatureStoreSpec{ + FeastProject: "test_project", + Services: &feastdevv1.FeatureStoreServices{ + OnlineStore: &feastdevv1.OnlineStore{}, + }, + }, + } + + ApplyDefaultsToStatus(cr) + + online := cr.Status.Applied.Services.OnlineStore + Expect(online).ToNot(BeNil()) + Expect(online.Persistence).ToNot(BeNil()) + Expect(online.Server).ToNot(BeNil()) + }) + + // #6586: disabling the online store opts out of its persistence and serving + // pod, letting a registry-only or offline-only FeatureStore skip it while + // leaving the default-on behavior unchanged for everyone else. + It("does not apply persistence or server defaults when the online store is disabled", func() { + cr := &feastdevv1.FeatureStore{ + Spec: feastdevv1.FeatureStoreSpec{ + FeastProject: "test_project", + Services: &feastdevv1.FeatureStoreServices{ + OnlineStore: &feastdevv1.OnlineStore{Disabled: true}, + }, + }, + } + + ApplyDefaultsToStatus(cr) + + online := cr.Status.Applied.Services.OnlineStore + Expect(online).ToNot(BeNil()) + Expect(online.Disabled).To(BeTrue()) + Expect(online.Persistence).To(BeNil()) + Expect(online.Server).To(BeNil()) + }) +})