From 7a1f4dd8b96a40d055467e1e5f72c91167e40484 Mon Sep 17 00:00:00 2001 From: Tommy Hughes IV Date: Fri, 17 Jan 2025 09:53:23 -0600 Subject: [PATCH] feat: Operator improvements (#4928) operator improvements Signed-off-by: Tommy Hughes --- .../api/v1alpha1/featurestore_types.go | 21 +- .../api/v1alpha1/zz_generated.deepcopy.go | 26 +- infra/feast-operator/bundle.Dockerfile | 2 +- ...er-manager-metrics-service_v1_service.yaml | 2 +- .../feast-operator.clusterserviceversion.yaml | 68 +- .../manifests/feast.dev_featurestores.yaml | 1960 ++++++++++++++++- .../bundle/metadata/annotations.yaml | 2 +- .../crd/bases/feast.dev_featurestores.yaml | 109 +- .../config/default/kustomization.yaml | 3 + .../default/manager_related_images_patch.yaml | 10 + .../config/manager/manager.yaml | 5 + infra/feast-operator/dist/install.yaml | 114 +- .../featurestore_controller_db_store_test.go | 10 +- .../featurestore_controller_ephemeral_test.go | 12 +- ...restore_controller_kubernetes_auth_test.go | 6 +- .../featurestore_controller_loglevel_test.go | 12 +- ...eaturestore_controller_objectstore_test.go | 14 +- .../featurestore_controller_oidc_auth_test.go | 16 +- .../featurestore_controller_pvc_test.go | 44 +- .../featurestore_controller_test.go | 37 +- .../featurestore_controller_tls_test.go | 10 +- .../controller/services/repo_config_test.go | 50 +- .../internal/controller/services/services.go | 109 +- .../controller/services/services_types.go | 1 + .../internal/controller/services/tls.go | 2 +- .../internal/controller/services/tls_test.go | 14 +- .../internal/controller/services/util.go | 58 +- infra/scripts/release/files_to_bump.txt | 1 + 28 files changed, 2491 insertions(+), 227 deletions(-) create mode 100644 infra/feast-operator/config/default/manager_related_images_patch.yaml diff --git a/infra/feast-operator/api/v1alpha1/featurestore_types.go b/infra/feast-operator/api/v1alpha1/featurestore_types.go index 2028f488285..b44f776dab4 100644 --- a/infra/feast-operator/api/v1alpha1/featurestore_types.go +++ b/infra/feast-operator/api/v1alpha1/featurestore_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -67,9 +68,12 @@ type FeatureStoreSpec struct { // FeatureStoreServices defines the desired feast services. An ephemeral registry is deployed by default. type FeatureStoreServices struct { - OfflineStore *OfflineStore `json:"offlineStore,omitempty"` - OnlineStore *OnlineStore `json:"onlineStore,omitempty"` - Registry *Registry `json:"registry,omitempty"` + OfflineStore *OfflineStore `json:"offlineStore,omitempty"` + OnlineStore *OnlineStore `json:"onlineStore,omitempty"` + Registry *Registry `json:"registry,omitempty"` + DeploymentStrategy *appsv1.DeploymentStrategy `json:"deploymentStrategy,omitempty"` + // Disable the 'feast repo initialization' initContainer + DisableInitContainers bool `json:"disableInitContainers,omitempty"` } // OfflineStore configures the deployed offline store service @@ -370,12 +374,11 @@ type FeatureStoreStatus struct { // Shows the currently applied feast configuration, including any pertinent defaults Applied FeatureStoreSpec `json:"applied,omitempty"` // ConfigMap in this namespace containing a client `feature_store.yaml` for this feast deployment - ClientConfigMap string `json:"clientConfigMap,omitempty"` - Conditions []metav1.Condition `json:"conditions,omitempty"` - // Version of feast that's currently deployed - FeastVersion string `json:"feastVersion,omitempty"` - Phase string `json:"phase,omitempty"` - ServiceHostnames ServiceHostnames `json:"serviceHostnames,omitempty"` + ClientConfigMap string `json:"clientConfigMap,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` + FeastVersion string `json:"feastVersion,omitempty"` + Phase string `json:"phase,omitempty"` + ServiceHostnames ServiceHostnames `json:"serviceHostnames,omitempty"` } // ServiceHostnames defines the service hostnames in the format of :, e.g. example.svc.cluster.local:80 diff --git a/infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go b/infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go index 72e6fc72007..6fbd44deb70 100644 --- a/infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,8 @@ limitations under the License. package v1alpha1 import ( - "k8s.io/api/core/v1" + "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -163,6 +164,11 @@ func (in *FeatureStoreServices) DeepCopyInto(out *FeatureStoreServices) { *out = new(Registry) (*in).DeepCopyInto(*out) } + if in.DeploymentStrategy != nil { + in, out := &in.DeploymentStrategy, &out.DeploymentStrategy + *out = new(v1.DeploymentStrategy) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureStoreServices. @@ -465,10 +471,10 @@ func (in *OptionalConfigs) DeepCopyInto(out *OptionalConfigs) { *out = *in if in.Env != nil { in, out := &in.Env, &out.Env - *out = new([]v1.EnvVar) + *out = new([]corev1.EnvVar) if **in != nil { in, out := *in, *out - *out = make([]v1.EnvVar, len(*in)) + *out = make([]corev1.EnvVar, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -476,10 +482,10 @@ func (in *OptionalConfigs) DeepCopyInto(out *OptionalConfigs) { } if in.EnvFrom != nil { in, out := &in.EnvFrom, &out.EnvFrom - *out = new([]v1.EnvFromSource) + *out = new([]corev1.EnvFromSource) if **in != nil { in, out := *in, *out - *out = make([]v1.EnvFromSource, len(*in)) + *out = make([]corev1.EnvFromSource, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -487,12 +493,12 @@ func (in *OptionalConfigs) DeepCopyInto(out *OptionalConfigs) { } if in.ImagePullPolicy != nil { in, out := &in.ImagePullPolicy, &out.ImagePullPolicy - *out = new(v1.PullPolicy) + *out = new(corev1.PullPolicy) **out = **in } if in.Resources != nil { in, out := &in.Resources, &out.Resources - *out = new(v1.ResourceRequirements) + *out = new(corev1.ResourceRequirements) (*in).DeepCopyInto(*out) } } @@ -512,7 +518,7 @@ func (in *PvcConfig) DeepCopyInto(out *PvcConfig) { *out = *in if in.Ref != nil { in, out := &in.Ref, &out.Ref - *out = new(v1.LocalObjectReference) + *out = new(corev1.LocalObjectReference) **out = **in } if in.Create != nil { @@ -537,7 +543,7 @@ func (in *PvcCreate) DeepCopyInto(out *PvcCreate) { *out = *in if in.AccessModes != nil { in, out := &in.AccessModes, &out.AccessModes - *out = make([]v1.PersistentVolumeAccessMode, len(*in)) + *out = make([]corev1.PersistentVolumeAccessMode, len(*in)) copy(*out, *in) } if in.StorageClassName != nil { @@ -737,7 +743,7 @@ func (in *TlsConfigs) DeepCopyInto(out *TlsConfigs) { *out = *in if in.SecretRef != nil { in, out := &in.SecretRef, &out.SecretRef - *out = new(v1.LocalObjectReference) + *out = new(corev1.LocalObjectReference) **out = **in } out.SecretKeyNames = in.SecretKeyNames diff --git a/infra/feast-operator/bundle.Dockerfile b/infra/feast-operator/bundle.Dockerfile index ab3f14a9da4..685b137b92a 100644 --- a/infra/feast-operator/bundle.Dockerfile +++ b/infra/feast-operator/bundle.Dockerfile @@ -6,7 +6,7 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=feast-operator LABEL operators.operatorframework.io.bundle.channels.v1=alpha -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.37.0 +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.38.0 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 diff --git a/infra/feast-operator/bundle/manifests/feast-operator-controller-manager-metrics-service_v1_service.yaml b/infra/feast-operator/bundle/manifests/feast-operator-controller-manager-metrics-service_v1_service.yaml index e0cd9dc2545..913517e198a 100644 --- a/infra/feast-operator/bundle/manifests/feast-operator-controller-manager-metrics-service_v1_service.yaml +++ b/infra/feast-operator/bundle/manifests/feast-operator-controller-manager-metrics-service_v1_service.yaml @@ -12,7 +12,7 @@ spec: - name: https port: 8443 protocol: TCP - targetPort: https + targetPort: 8443 selector: control-plane: controller-manager status: diff --git a/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml b/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml index 245db443581..bfd32b5a830 100644 --- a/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml +++ b/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml @@ -16,10 +16,10 @@ metadata: } ] capabilities: Basic Install - createdAt: "2024-11-01T13:05:11Z" - operators.operatorframework.io/builder: operator-sdk-v1.37.0 + createdAt: "2025-01-16T22:15:56Z" + operators.operatorframework.io/builder: operator-sdk-v1.38.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 - name: feast-operator.v0.41.0 + name: feast-operator.v0.42.0 namespace: placeholder spec: apiservicedefinitions: {} @@ -54,6 +54,8 @@ spec: - "" resources: - configmaps + - persistentvolumeclaims + - serviceaccounts - services verbs: - create @@ -62,6 +64,13 @@ spec: - list - update - watch + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list - apiGroups: - feast.dev resources: @@ -88,6 +97,18 @@ spec: - get - patch - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - delete + - get + - list + - update + - watch - apiGroups: - authentication.k8s.io resources: @@ -122,35 +143,17 @@ spec: spec: containers: - args: - - --secure-listen-address=0.0.0.0:8443 - - --upstream=http://127.0.0.1:8080/ - - --logtostderr=true - - --v=0 - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.16.0 - name: kube-rbac-proxy - ports: - - containerPort: 8443 - name: https - protocol: TCP - resources: - limits: - cpu: 500m - memory: 128Mi - requests: - cpu: 5m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - - args: - - --health-probe-bind-address=:8081 - - --metrics-bind-address=127.0.0.1:8080 + - --metrics-bind-address=:8443 - --leader-elect + - --health-probe-bind-address=:8081 command: - /manager - image: feastdev/feast-operator:0.41.0 + env: + - name: RELATED_IMAGE_FEATURE_SERVER + value: docker.io/feastdev/feature-server:0.42.0 + - name: RELATED_IMAGE_GRPC_CURL + value: docker.io/fullstorydev/grpcurl:v1.9.1-alpine + image: feastdev/feast-operator:0.42.0 livenessProbe: httpGet: path: /healthz @@ -239,4 +242,9 @@ spec: provider: name: Feast Community url: https://lf-aidata.atlassian.net/wiki/spaces/FEAST/ - version: 0.41.0 + relatedImages: + - image: docker.io/feastdev/feature-server:0.42.0 + name: feature-server + - image: docker.io/fullstorydev/grpcurl:v1.9.1-alpine + name: grpc-curl + version: 0.42.0 diff --git a/infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml b/infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml index 2142e093eb1..ff1a77936f2 100644 --- a/infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml +++ b/infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.15.0 creationTimestamp: null name: featurestores.feast.dev spec: @@ -48,6 +48,57 @@ spec: spec: description: FeatureStoreSpec defines the desired state of FeatureStore properties: + authz: + description: AuthzConfig defines the authorization settings for the + deployed Feast services. + properties: + kubernetes: + description: |- + KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. + https://kubernetes.io/docs/reference/access-authn-authz/rbac/ + properties: + roles: + description: |- + The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. + Roles are managed by the operator and created with an empty list of rules. + See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission + The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. + This configuration option is only providing a way to automate this procedure. + Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. + items: + type: string + type: array + type: object + oidc: + description: |- + OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. + https://auth0.com/docs/authenticate/protocols/openid-connect-protocol + properties: + secretRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - secretRef + type: object + type: object + x-kubernetes-validations: + - message: One selection required between kubernetes or oidc. + rule: '[has(self.kubernetes), has(self.oidc)].exists_one(c, c)' feastProject: description: FeastProject is the Feast project id. This can be any alphanumeric string with underscores, but it cannot start with an @@ -55,9 +106,63 @@ spec: pattern: ^[A-Za-z0-9][A-Za-z0-9_]*$ type: string services: - description: FeatureStoreServices defines the desired feast service - deployments. ephemeral registry is deployed by default. + description: FeatureStoreServices defines the desired feast services. + An ephemeral registry is deployed by default. properties: + deploymentStrategy: + description: DeploymentStrategy describes how to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + --- + TODO: Update this to follow our convention for oneOf, whatever we decide it + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Can be "Recreate" or "RollingUpdate". + Default is RollingUpdate. + type: string + type: object + disableInitContainers: + description: Disable the 'feast repo initialization' initContainer + type: boolean offlineStore: description: OfflineStore configures the deployed offline store service @@ -94,10 +199,15 @@ spec: description: The key to select. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the ConfigMap or @@ -157,10 +267,15 @@ spec: from. Must be a valid secret key. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the Secret or its @@ -175,12 +290,223 @@ spec: - name type: object type: array + envFrom: + items: + description: EnvFromSource represents the source of a set + of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend to each + key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array image: type: string imagePullPolicy: description: PullPolicy describes a policy for if/when to pull a container image type: string + logLevel: + description: |- + LogLevel sets the logging level for the offline store service + Allowed values: "debug", "info", "warning", "error", "critical". + enum: + - debug + - info + - warning + - error + - critical + type: string + persistence: + description: OfflineStorePersistence configures the persistence + settings for the offline store service + properties: + file: + description: OfflineStoreFilePersistence configures the + file-based persistence for the offline store service + properties: + pvc: + description: |- + PvcConfig defines the settings for a persistent file store based on PVCs. + We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. + properties: + create: + description: Settings for creating a new PVC + properties: + accessModes: + description: AccessModes k8s persistent volume + access modes. Defaults to ["ReadWriteOnce"]. + items: + type: string + type: array + resources: + description: |- + Resources describes the storage resource requirements for a volume. + Default requested storage size depends on the associated service: + - 10Gi for offline store + - 5Gi for online store + - 5Gi for registry + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + storageClassName: + description: |- + StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value + means that this volume does not belong to any StorageClass and the cluster default will be used. + type: string + type: object + x-kubernetes-validations: + - message: PvcCreate is immutable + rule: self == oldSelf + mountPath: + description: |- + MountPath within the container at which the volume should be mounted. + Must start by "/" and cannot contain ':'. + type: string + ref: + description: Reference to an existing field + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - mountPath + type: object + x-kubernetes-validations: + - message: One selection is required between ref and + create. + rule: '[has(self.ref), has(self.create)].exists_one(c, + c)' + - message: Mount path must start with '/' and must + not contain ':' + rule: self.mountPath.matches('^/[^:]*$') + type: + enum: + - file + - dask + - duckdb + type: string + type: object + store: + description: OfflineStoreDBStorePersistence configures + the DB store persistence for the offline store service + properties: + secretKeyName: + description: By default, the selected store "type" + is used as the SecretKeyName + type: string + secretRef: + description: Data store parameters should be placed + as-is from the "feature_store.yaml" under the secret + key. "registry_type" & "type" fields should be removed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: + enum: + - snowflake.offline + - bigquery + - redshift + - spark + - postgres + - trino + - redis + - athena + - mssql + type: string + required: + - secretRef + - type + type: object + type: object + x-kubernetes-validations: + - message: One selection required between file or store. + rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: description: ResourceRequirements describes the compute resource requirements. @@ -237,6 +563,49 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object + tls: + description: TlsConfigs configures server TLS for a feast + service. in an openshift cluster, this is configured by + default using service serving certificates. + properties: + disable: + description: will disable TLS for the feast service. useful + in an openshift cluster, for example, where TLS is configured + by default + type: boolean + secretKeyNames: + description: SecretKeyNames defines the secret key names + for the TLS key and cert. + properties: + tlsCrt: + description: defaults to "tls.crt" + type: string + tlsKey: + description: defaults to "tls.key" + type: string + type: object + secretRef: + description: references the local k8s secret where the + TLS key and cert reside + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' type: object onlineStore: description: OnlineStore configures the deployed online store @@ -274,10 +643,15 @@ spec: description: The key to select. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the ConfigMap or @@ -337,10 +711,15 @@ spec: from. Must be a valid secret key. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the Secret or its @@ -355,12 +734,237 @@ spec: - name type: object type: array + envFrom: + items: + description: EnvFromSource represents the source of a set + of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend to each + key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array image: type: string imagePullPolicy: description: PullPolicy describes a policy for if/when to pull a container image type: string + logLevel: + description: |- + LogLevel sets the logging level for the online store service + Allowed values: "debug", "info", "warning", "error", "critical". + enum: + - debug + - info + - warning + - error + - critical + type: string + persistence: + description: OnlineStorePersistence configures the persistence + settings for the online store service + properties: + file: + description: OnlineStoreFilePersistence configures the + file-based persistence for the offline store service + properties: + path: + type: string + pvc: + description: |- + PvcConfig defines the settings for a persistent file store based on PVCs. + We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. + properties: + create: + description: Settings for creating a new PVC + properties: + accessModes: + description: AccessModes k8s persistent volume + access modes. Defaults to ["ReadWriteOnce"]. + items: + type: string + type: array + resources: + description: |- + Resources describes the storage resource requirements for a volume. + Default requested storage size depends on the associated service: + - 10Gi for offline store + - 5Gi for online store + - 5Gi for registry + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + storageClassName: + description: |- + StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value + means that this volume does not belong to any StorageClass and the cluster default will be used. + type: string + type: object + x-kubernetes-validations: + - message: PvcCreate is immutable + rule: self == oldSelf + mountPath: + description: |- + MountPath within the container at which the volume should be mounted. + Must start by "/" and cannot contain ':'. + type: string + ref: + description: Reference to an existing field + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - mountPath + type: object + x-kubernetes-validations: + - message: One selection is required between ref and + create. + rule: '[has(self.ref), has(self.create)].exists_one(c, + c)' + - message: Mount path must start with '/' and must + not contain ':' + rule: self.mountPath.matches('^/[^:]*$') + type: object + x-kubernetes-validations: + - message: Ephemeral stores must have absolute paths. + rule: '(!has(self.pvc) && has(self.path)) ? self.path.startsWith(''/'') + : true' + - message: PVC path must be a file name only, with no + slashes. + rule: '(has(self.pvc) && has(self.path)) ? !self.path.startsWith(''/'') + : true' + - message: Online store does not support S3 or GS buckets. + rule: 'has(self.path) ? !(self.path.startsWith(''s3://'') + || self.path.startsWith(''gs://'')) : true' + store: + description: OnlineStoreDBStorePersistence configures + the DB store persistence for the offline store service + properties: + secretKeyName: + description: By default, the selected store "type" + is used as the SecretKeyName + type: string + secretRef: + description: Data store parameters should be placed + as-is from the "feature_store.yaml" under the secret + key. "registry_type" & "type" fields should be removed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: + enum: + - snowflake.online + - redis + - ikv + - datastore + - dynamodb + - bigtable + - postgres + - cassandra + - mysql + - hazelcast + - singlestore + - hbase + - elasticsearch + - qdrant + - couchbase + - milvus + type: string + required: + - secretRef + - type + type: object + type: object + x-kubernetes-validations: + - message: One selection required between file or store. + rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: description: ResourceRequirements describes the compute resource requirements. @@ -417,6 +1021,49 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object + tls: + description: TlsConfigs configures server TLS for a feast + service. in an openshift cluster, this is configured by + default using service serving certificates. + properties: + disable: + description: will disable TLS for the feast service. useful + in an openshift cluster, for example, where TLS is configured + by default + type: boolean + secretKeyNames: + description: SecretKeyNames defines the secret key names + for the TLS key and cert. + properties: + tlsCrt: + description: defaults to "tls.crt" + type: string + tlsKey: + description: defaults to "tls.key" + type: string + type: object + secretRef: + description: references the local k8s secret where the + TLS key and cert reside + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' type: object registry: description: Registry configures the registry service. One selection @@ -458,10 +1105,15 @@ spec: description: The key to select. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the ConfigMap @@ -522,10 +1174,15 @@ spec: from. Must be a valid secret key. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the Secret @@ -540,12 +1197,237 @@ spec: - name type: object type: array + envFrom: + items: + description: EnvFromSource represents the source of + a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the Secret must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array image: type: string imagePullPolicy: description: PullPolicy describes a policy for if/when to pull a container image type: string + logLevel: + description: |- + LogLevel sets the logging level for the registry service + Allowed values: "debug", "info", "warning", "error", "critical". + enum: + - debug + - info + - warning + - error + - critical + type: string + persistence: + description: RegistryPersistence configures the persistence + settings for the registry service + properties: + file: + description: RegistryFilePersistence configures the + file-based persistence for the registry service + properties: + path: + type: string + pvc: + description: |- + PvcConfig defines the settings for a persistent file store based on PVCs. + We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. + properties: + create: + description: Settings for creating a new PVC + properties: + accessModes: + description: AccessModes k8s persistent + volume access modes. Defaults to ["ReadWriteOnce"]. + items: + type: string + type: array + resources: + description: |- + Resources describes the storage resource requirements for a volume. + Default requested storage size depends on the associated service: + - 10Gi for offline store + - 5Gi for online store + - 5Gi for registry + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + storageClassName: + description: |- + StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value + means that this volume does not belong to any StorageClass and the cluster default will be used. + type: string + type: object + x-kubernetes-validations: + - message: PvcCreate is immutable + rule: self == oldSelf + mountPath: + description: |- + MountPath within the container at which the volume should be mounted. + Must start by "/" and cannot contain ':'. + type: string + ref: + description: Reference to an existing field + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - mountPath + type: object + x-kubernetes-validations: + - message: One selection is required between ref + and create. + rule: '[has(self.ref), has(self.create)].exists_one(c, + c)' + - message: Mount path must start with '/' and + must not contain ':' + rule: self.mountPath.matches('^/[^:]*$') + s3_additional_kwargs: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-validations: + - message: Registry files must use absolute paths + or be S3 ('s3://') or GS ('gs://') object store + URIs. + rule: '(!has(self.pvc) && has(self.path)) ? (self.path.startsWith(''/'') + || self.path.startsWith(''s3://'') || self.path.startsWith(''gs://'')) + : true' + - message: PVC path must be a file name only, with + no slashes. + rule: '(has(self.pvc) && has(self.path)) ? !self.path.startsWith(''/'') + : true' + - message: PVC persistence does not support S3 or + GS object store URIs. + rule: '(has(self.pvc) && has(self.path)) ? !(self.path.startsWith(''s3://'') + || self.path.startsWith(''gs://'')) : true' + - message: Additional S3 settings are available only + for S3 object store URIs. + rule: '(has(self.s3_additional_kwargs) && has(self.path)) + ? self.path.startsWith(''s3://'') : true' + store: + description: RegistryDBStorePersistence configures + the DB store persistence for the registry service + properties: + secretKeyName: + description: By default, the selected store "type" + is used as the SecretKeyName + type: string + secretRef: + description: Data store parameters should be placed + as-is from the "feature_store.yaml" under the + secret key. "registry_type" & "type" fields + should be removed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: + enum: + - sql + - snowflake.registry + type: string + required: + - secretRef + - type + type: object + type: object + x-kubernetes-validations: + - message: One selection required between file or store. + rule: '[has(self.file), has(self.store)].exists_one(c, + c)' resources: description: ResourceRequirements describes the compute resource requirements. @@ -603,6 +1485,49 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object + tls: + description: TlsConfigs configures server TLS for a feast + service. in an openshift cluster, this is configured + by default using service serving certificates. + properties: + disable: + description: will disable TLS for the feast service. + useful in an openshift cluster, for example, where + TLS is configured by default + type: boolean + secretKeyNames: + description: SecretKeyNames defines the secret key + names for the TLS key and cert. + properties: + tlsCrt: + description: defaults to "tls.crt" + type: string + tlsKey: + description: defaults to "tls.key" + type: string + type: object + secretRef: + description: references the local k8s secret where + the TLS key and cert reside + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' type: object remote: description: |- @@ -626,6 +1551,37 @@ spec: description: Host address of the remote registry service - :, e.g. `registry..svc.cluster.local:80` type: string + tls: + description: TlsRemoteRegistryConfigs configures client + TLS for a remote feast registry. in an openshift cluster, + this is configured by default when the remote feast + registry is using service serving certificates. + properties: + certName: + description: defines the configmap key name for the + client TLS cert. + type: string + configMapRef: + description: references the local k8s configmap where + the TLS cert resides + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - certName + - configMapRef + type: object type: object x-kubernetes-validations: - message: One selection required. @@ -646,6 +1602,58 @@ spec: description: Shows the currently applied feast configuration, including any pertinent defaults properties: + authz: + description: AuthzConfig defines the authorization settings for + the deployed Feast services. + properties: + kubernetes: + description: |- + KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. + https://kubernetes.io/docs/reference/access-authn-authz/rbac/ + properties: + roles: + description: |- + The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. + Roles are managed by the operator and created with an empty list of rules. + See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission + The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. + This configuration option is only providing a way to automate this procedure. + Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. + items: + type: string + type: array + type: object + oidc: + description: |- + OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. + https://auth0.com/docs/authenticate/protocols/openid-connect-protocol + properties: + secretRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - secretRef + type: object + type: object + x-kubernetes-validations: + - message: One selection required between kubernetes or oidc. + rule: '[has(self.kubernetes), has(self.oidc)].exists_one(c, + c)' feastProject: description: FeastProject is the Feast project id. This can be any alphanumeric string with underscores, but it cannot start @@ -653,9 +1661,63 @@ spec: pattern: ^[A-Za-z0-9][A-Za-z0-9_]*$ type: string services: - description: FeatureStoreServices defines the desired feast service - deployments. ephemeral registry is deployed by default. + description: FeatureStoreServices defines the desired feast services. + An ephemeral registry is deployed by default. properties: + deploymentStrategy: + description: DeploymentStrategy describes how to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + --- + TODO: Update this to follow our convention for oneOf, whatever we decide it + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Can be "Recreate" or + "RollingUpdate". Default is RollingUpdate. + type: string + type: object + disableInitContainers: + description: Disable the 'feast repo initialization' initContainer + type: boolean offlineStore: description: OfflineStore configures the deployed offline store service @@ -692,10 +1754,15 @@ spec: description: The key to select. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the ConfigMap @@ -756,10 +1823,15 @@ spec: from. Must be a valid secret key. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the Secret @@ -774,12 +1846,226 @@ spec: - name type: object type: array + envFrom: + items: + description: EnvFromSource represents the source of + a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the Secret must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array image: type: string imagePullPolicy: description: PullPolicy describes a policy for if/when to pull a container image type: string + logLevel: + description: |- + LogLevel sets the logging level for the offline store service + Allowed values: "debug", "info", "warning", "error", "critical". + enum: + - debug + - info + - warning + - error + - critical + type: string + persistence: + description: OfflineStorePersistence configures the persistence + settings for the offline store service + properties: + file: + description: OfflineStoreFilePersistence configures + the file-based persistence for the offline store + service + properties: + pvc: + description: |- + PvcConfig defines the settings for a persistent file store based on PVCs. + We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. + properties: + create: + description: Settings for creating a new PVC + properties: + accessModes: + description: AccessModes k8s persistent + volume access modes. Defaults to ["ReadWriteOnce"]. + items: + type: string + type: array + resources: + description: |- + Resources describes the storage resource requirements for a volume. + Default requested storage size depends on the associated service: + - 10Gi for offline store + - 5Gi for online store + - 5Gi for registry + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + storageClassName: + description: |- + StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value + means that this volume does not belong to any StorageClass and the cluster default will be used. + type: string + type: object + x-kubernetes-validations: + - message: PvcCreate is immutable + rule: self == oldSelf + mountPath: + description: |- + MountPath within the container at which the volume should be mounted. + Must start by "/" and cannot contain ':'. + type: string + ref: + description: Reference to an existing field + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - mountPath + type: object + x-kubernetes-validations: + - message: One selection is required between ref + and create. + rule: '[has(self.ref), has(self.create)].exists_one(c, + c)' + - message: Mount path must start with '/' and + must not contain ':' + rule: self.mountPath.matches('^/[^:]*$') + type: + enum: + - file + - dask + - duckdb + type: string + type: object + store: + description: OfflineStoreDBStorePersistence configures + the DB store persistence for the offline store service + properties: + secretKeyName: + description: By default, the selected store "type" + is used as the SecretKeyName + type: string + secretRef: + description: Data store parameters should be placed + as-is from the "feature_store.yaml" under the + secret key. "registry_type" & "type" fields + should be removed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: + enum: + - snowflake.offline + - bigquery + - redshift + - spark + - postgres + - trino + - redis + - athena + - mssql + type: string + required: + - secretRef + - type + type: object + type: object + x-kubernetes-validations: + - message: One selection required between file or store. + rule: '[has(self.file), has(self.store)].exists_one(c, + c)' resources: description: ResourceRequirements describes the compute resource requirements. @@ -837,6 +2123,49 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object + tls: + description: TlsConfigs configures server TLS for a feast + service. in an openshift cluster, this is configured + by default using service serving certificates. + properties: + disable: + description: will disable TLS for the feast service. + useful in an openshift cluster, for example, where + TLS is configured by default + type: boolean + secretKeyNames: + description: SecretKeyNames defines the secret key + names for the TLS key and cert. + properties: + tlsCrt: + description: defaults to "tls.crt" + type: string + tlsKey: + description: defaults to "tls.key" + type: string + type: object + secretRef: + description: references the local k8s secret where + the TLS key and cert reside + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' type: object onlineStore: description: OnlineStore configures the deployed online store @@ -874,10 +2203,15 @@ spec: description: The key to select. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the ConfigMap @@ -938,10 +2272,15 @@ spec: from. Must be a valid secret key. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the Secret @@ -956,12 +2295,241 @@ spec: - name type: object type: array + envFrom: + items: + description: EnvFromSource represents the source of + a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the Secret must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array image: type: string imagePullPolicy: description: PullPolicy describes a policy for if/when to pull a container image type: string + logLevel: + description: |- + LogLevel sets the logging level for the online store service + Allowed values: "debug", "info", "warning", "error", "critical". + enum: + - debug + - info + - warning + - error + - critical + type: string + persistence: + description: OnlineStorePersistence configures the persistence + settings for the online store service + properties: + file: + description: OnlineStoreFilePersistence configures + the file-based persistence for the offline store + service + properties: + path: + type: string + pvc: + description: |- + PvcConfig defines the settings for a persistent file store based on PVCs. + We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. + properties: + create: + description: Settings for creating a new PVC + properties: + accessModes: + description: AccessModes k8s persistent + volume access modes. Defaults to ["ReadWriteOnce"]. + items: + type: string + type: array + resources: + description: |- + Resources describes the storage resource requirements for a volume. + Default requested storage size depends on the associated service: + - 10Gi for offline store + - 5Gi for online store + - 5Gi for registry + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + storageClassName: + description: |- + StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value + means that this volume does not belong to any StorageClass and the cluster default will be used. + type: string + type: object + x-kubernetes-validations: + - message: PvcCreate is immutable + rule: self == oldSelf + mountPath: + description: |- + MountPath within the container at which the volume should be mounted. + Must start by "/" and cannot contain ':'. + type: string + ref: + description: Reference to an existing field + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - mountPath + type: object + x-kubernetes-validations: + - message: One selection is required between ref + and create. + rule: '[has(self.ref), has(self.create)].exists_one(c, + c)' + - message: Mount path must start with '/' and + must not contain ':' + rule: self.mountPath.matches('^/[^:]*$') + type: object + x-kubernetes-validations: + - message: Ephemeral stores must have absolute paths. + rule: '(!has(self.pvc) && has(self.path)) ? self.path.startsWith(''/'') + : true' + - message: PVC path must be a file name only, with + no slashes. + rule: '(has(self.pvc) && has(self.path)) ? !self.path.startsWith(''/'') + : true' + - message: Online store does not support S3 or GS + buckets. + rule: 'has(self.path) ? !(self.path.startsWith(''s3://'') + || self.path.startsWith(''gs://'')) : true' + store: + description: OnlineStoreDBStorePersistence configures + the DB store persistence for the offline store service + properties: + secretKeyName: + description: By default, the selected store "type" + is used as the SecretKeyName + type: string + secretRef: + description: Data store parameters should be placed + as-is from the "feature_store.yaml" under the + secret key. "registry_type" & "type" fields + should be removed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: + enum: + - snowflake.online + - redis + - ikv + - datastore + - dynamodb + - bigtable + - postgres + - cassandra + - mysql + - hazelcast + - singlestore + - hbase + - elasticsearch + - qdrant + - couchbase + - milvus + type: string + required: + - secretRef + - type + type: object + type: object + x-kubernetes-validations: + - message: One selection required between file or store. + rule: '[has(self.file), has(self.store)].exists_one(c, + c)' resources: description: ResourceRequirements describes the compute resource requirements. @@ -1019,6 +2587,49 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object + tls: + description: TlsConfigs configures server TLS for a feast + service. in an openshift cluster, this is configured + by default using service serving certificates. + properties: + disable: + description: will disable TLS for the feast service. + useful in an openshift cluster, for example, where + TLS is configured by default + type: boolean + secretKeyNames: + description: SecretKeyNames defines the secret key + names for the TLS key and cert. + properties: + tlsCrt: + description: defaults to "tls.crt" + type: string + tlsKey: + description: defaults to "tls.key" + type: string + type: object + secretRef: + description: references the local k8s secret where + the TLS key and cert reside + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' type: object registry: description: Registry configures the registry service. One @@ -1060,10 +2671,15 @@ spec: description: The key to select. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the ConfigMap @@ -1127,10 +2743,15 @@ spec: key. type: string name: + default: "" description: |- Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: description: Specify whether the Secret @@ -1145,12 +2766,242 @@ spec: - name type: object type: array + envFrom: + items: + description: EnvFromSource represents the source + of a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array image: type: string imagePullPolicy: description: PullPolicy describes a policy for if/when to pull a container image type: string + logLevel: + description: |- + LogLevel sets the logging level for the registry service + Allowed values: "debug", "info", "warning", "error", "critical". + enum: + - debug + - info + - warning + - error + - critical + type: string + persistence: + description: RegistryPersistence configures the persistence + settings for the registry service + properties: + file: + description: RegistryFilePersistence configures + the file-based persistence for the registry + service + properties: + path: + type: string + pvc: + description: |- + PvcConfig defines the settings for a persistent file store based on PVCs. + We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. + properties: + create: + description: Settings for creating a new + PVC + properties: + accessModes: + description: AccessModes k8s persistent + volume access modes. Defaults to + ["ReadWriteOnce"]. + items: + type: string + type: array + resources: + description: |- + Resources describes the storage resource requirements for a volume. + Default requested storage size depends on the associated service: + - 10Gi for offline store + - 5Gi for online store + - 5Gi for registry + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + storageClassName: + description: |- + StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value + means that this volume does not belong to any StorageClass and the cluster default will be used. + type: string + type: object + x-kubernetes-validations: + - message: PvcCreate is immutable + rule: self == oldSelf + mountPath: + description: |- + MountPath within the container at which the volume should be mounted. + Must start by "/" and cannot contain ':'. + type: string + ref: + description: Reference to an existing + field + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - mountPath + type: object + x-kubernetes-validations: + - message: One selection is required between + ref and create. + rule: '[has(self.ref), has(self.create)].exists_one(c, + c)' + - message: Mount path must start with '/' + and must not contain ':' + rule: self.mountPath.matches('^/[^:]*$') + s3_additional_kwargs: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-validations: + - message: Registry files must use absolute paths + or be S3 ('s3://') or GS ('gs://') object + store URIs. + rule: '(!has(self.pvc) && has(self.path)) ? + (self.path.startsWith(''/'') || self.path.startsWith(''s3://'') + || self.path.startsWith(''gs://'')) : true' + - message: PVC path must be a file name only, + with no slashes. + rule: '(has(self.pvc) && has(self.path)) ? !self.path.startsWith(''/'') + : true' + - message: PVC persistence does not support S3 + or GS object store URIs. + rule: '(has(self.pvc) && has(self.path)) ? !(self.path.startsWith(''s3://'') + || self.path.startsWith(''gs://'')) : true' + - message: Additional S3 settings are available + only for S3 object store URIs. + rule: '(has(self.s3_additional_kwargs) && has(self.path)) + ? self.path.startsWith(''s3://'') : true' + store: + description: RegistryDBStorePersistence configures + the DB store persistence for the registry service + properties: + secretKeyName: + description: By default, the selected store + "type" is used as the SecretKeyName + type: string + secretRef: + description: Data store parameters should + be placed as-is from the "feature_store.yaml" + under the secret key. "registry_type" & + "type" fields should be removed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: + enum: + - sql + - snowflake.registry + type: string + required: + - secretRef + - type + type: object + type: object + x-kubernetes-validations: + - message: One selection required between file or + store. + rule: '[has(self.file), has(self.store)].exists_one(c, + c)' resources: description: ResourceRequirements describes the compute resource requirements. @@ -1208,6 +3059,49 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object + tls: + description: TlsConfigs configures server TLS for + a feast service. in an openshift cluster, this is + configured by default using service serving certificates. + properties: + disable: + description: will disable TLS for the feast service. + useful in an openshift cluster, for example, + where TLS is configured by default + type: boolean + secretKeyNames: + description: SecretKeyNames defines the secret + key names for the TLS key and cert. + properties: + tlsCrt: + description: defaults to "tls.crt" + type: string + tlsKey: + description: defaults to "tls.key" + type: string + type: object + secretRef: + description: references the local k8s secret where + the TLS key and cert reside + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' type: object remote: description: |- @@ -1231,6 +3125,37 @@ spec: description: Host address of the remote registry service - :, e.g. `registry..svc.cluster.local:80` type: string + tls: + description: TlsRemoteRegistryConfigs configures client + TLS for a remote feast registry. in an openshift + cluster, this is configured by default when the + remote feast registry is using service serving certificates. + properties: + certName: + description: defines the configmap key name for + the client TLS cert. + type: string + configMapRef: + description: references the local k8s configmap + where the TLS cert resides + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + required: + - certName + - configMapRef + type: object type: object x-kubernetes-validations: - message: One selection required. @@ -1319,7 +3244,6 @@ spec: type: object type: array feastVersion: - description: Version of feast that's currently deployed type: string phase: type: string diff --git a/infra/feast-operator/bundle/metadata/annotations.yaml b/infra/feast-operator/bundle/metadata/annotations.yaml index bf929b9755b..5e280a43e24 100644 --- a/infra/feast-operator/bundle/metadata/annotations.yaml +++ b/infra/feast-operator/bundle/metadata/annotations.yaml @@ -5,7 +5,7 @@ annotations: operators.operatorframework.io.bundle.metadata.v1: metadata/ operators.operatorframework.io.bundle.package.v1: feast-operator operators.operatorframework.io.bundle.channels.v1: alpha - operators.operatorframework.io.metrics.builder: operator-sdk-v1.37.0 + operators.operatorframework.io.metrics.builder: operator-sdk-v1.38.0 operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 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 88254d73b9f..a509caf329f 100644 --- a/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml +++ b/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml @@ -109,6 +109,60 @@ spec: description: FeatureStoreServices defines the desired feast services. An ephemeral registry is deployed by default. properties: + deploymentStrategy: + description: DeploymentStrategy describes how to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + --- + TODO: Update this to follow our convention for oneOf, whatever we decide it + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Can be "Recreate" or "RollingUpdate". + Default is RollingUpdate. + type: string + type: object + disableInitContainers: + description: Disable the 'feast repo initialization' initContainer + type: boolean offlineStore: description: OfflineStore configures the deployed offline store service @@ -1610,6 +1664,60 @@ spec: description: FeatureStoreServices defines the desired feast services. An ephemeral registry is deployed by default. properties: + deploymentStrategy: + description: DeploymentStrategy describes how to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + --- + TODO: Update this to follow our convention for oneOf, whatever we decide it + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Can be "Recreate" or + "RollingUpdate". Default is RollingUpdate. + type: string + type: object + disableInitContainers: + description: Disable the 'feast repo initialization' initContainer + type: boolean offlineStore: description: OfflineStore configures the deployed offline store service @@ -3136,7 +3244,6 @@ spec: type: object type: array feastVersion: - description: Version of feast that's currently deployed type: string phase: type: string diff --git a/infra/feast-operator/config/default/kustomization.yaml b/infra/feast-operator/config/default/kustomization.yaml index dc1504e24a4..01534ae5cc8 100644 --- a/infra/feast-operator/config/default/kustomization.yaml +++ b/infra/feast-operator/config/default/kustomization.yaml @@ -30,6 +30,9 @@ resources: # Uncomment the patches line if you enable Metrics, and/or are using webhooks and cert-manager patches: +- path: manager_related_images_patch.yaml + target: + kind: Deployment # [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. # More info: https://book.kubebuilder.io/reference/metrics - path: manager_metrics_patch.yaml diff --git a/infra/feast-operator/config/default/manager_related_images_patch.yaml b/infra/feast-operator/config/default/manager_related_images_patch.yaml new file mode 100644 index 00000000000..7ad1ab5970f --- /dev/null +++ b/infra/feast-operator/config/default/manager_related_images_patch.yaml @@ -0,0 +1,10 @@ +- op: replace + path: "/spec/template/spec/containers/0/env/0" + value: + name: RELATED_IMAGE_FEATURE_SERVER + value: docker.io/feastdev/feature-server:0.42.0 +- op: replace + path: "/spec/template/spec/containers/0/env/1" + value: + name: RELATED_IMAGE_GRPC_CURL + value: docker.io/fullstorydev/grpcurl:v1.9.1-alpine diff --git a/infra/feast-operator/config/manager/manager.yaml b/infra/feast-operator/config/manager/manager.yaml index c0263e3a1c0..4259cf8a7e0 100644 --- a/infra/feast-operator/config/manager/manager.yaml +++ b/infra/feast-operator/config/manager/manager.yaml @@ -70,6 +70,11 @@ spec: capabilities: drop: - "ALL" + env: + - name: RELATED_IMAGE_FEATURE_SERVER + value: feast:latest + - name: RELATED_IMAGE_GRPC_CURL + value: grpc:latest livenessProbe: httpGet: path: /healthz diff --git a/infra/feast-operator/dist/install.yaml b/infra/feast-operator/dist/install.yaml index 82d6845c229..ae9a37d8c9c 100644 --- a/infra/feast-operator/dist/install.yaml +++ b/infra/feast-operator/dist/install.yaml @@ -117,6 +117,60 @@ spec: description: FeatureStoreServices defines the desired feast services. An ephemeral registry is deployed by default. properties: + deploymentStrategy: + description: DeploymentStrategy describes how to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + --- + TODO: Update this to follow our convention for oneOf, whatever we decide it + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Can be "Recreate" or "RollingUpdate". + Default is RollingUpdate. + type: string + type: object + disableInitContainers: + description: Disable the 'feast repo initialization' initContainer + type: boolean offlineStore: description: OfflineStore configures the deployed offline store service @@ -1618,6 +1672,60 @@ spec: description: FeatureStoreServices defines the desired feast services. An ephemeral registry is deployed by default. properties: + deploymentStrategy: + description: DeploymentStrategy describes how to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + --- + TODO: Update this to follow our convention for oneOf, whatever we decide it + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Can be "Recreate" or + "RollingUpdate". Default is RollingUpdate. + type: string + type: object + disableInitContainers: + description: Disable the 'feast repo initialization' initContainer + type: boolean offlineStore: description: OfflineStore configures the deployed offline store service @@ -3144,7 +3252,6 @@ spec: type: object type: array feastVersion: - description: Version of feast that's currently deployed type: string phase: type: string @@ -3471,6 +3578,11 @@ spec: - --health-probe-bind-address=:8081 command: - /manager + env: + - name: RELATED_IMAGE_FEATURE_SERVER + value: docker.io/feastdev/feature-server:0.42.0 + - name: RELATED_IMAGE_GRPC_CURL + value: docker.io/fullstorydev/grpcurl:v1.9.1-alpine image: feastdev/feast-operator:0.42.0 livenessProbe: httpGet: 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 48013c453c8..1c9fa4bc1a5 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 @@ -562,7 +562,7 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - registryContainer := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(registryContainer.Env) Expect(env).NotTo(BeNil()) @@ -600,7 +600,7 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { } Expect(repoConfig).To(Equal(testConfig)) - offlineContainer := services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer := services.GetOfflineContainer(*deploy) Expect(offlineContainer.Env).To(HaveLen(1)) assertEnvFrom(*offlineContainer) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) @@ -617,7 +617,7 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { Expect(err).NotTo(HaveOccurred()) Expect(repoConfigOffline).To(Equal(testConfig)) - onlineContainer := services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer := services.GetOnlineContainer(*deploy) Expect(onlineContainer.VolumeMounts).To(HaveLen(1)) Expect(onlineContainer.Env).To(HaveLen(1)) assertEnvFrom(*onlineContainer) @@ -635,7 +635,7 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { err = yaml.Unmarshal(envByte, repoConfigOnline) Expect(err).NotTo(HaveOccurred()) Expect(repoConfigOnline).To(Equal(testConfig)) - onlineContainer = services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer = services.GetOnlineContainer(*deploy) Expect(onlineContainer.Env).To(HaveLen(1)) // check client config @@ -698,7 +698,7 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - onlineContainer = services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer = services.GetOnlineContainer(*deploy) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) Expect(env).NotTo(BeNil()) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_ephemeral_test.go b/infra/feast-operator/internal/controller/featurestore_controller_ephemeral_test.go index dba603cbfa6..381a2d40a1d 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_ephemeral_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_ephemeral_test.go @@ -278,7 +278,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - registryContainer := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(registryContainer.Env) Expect(env).NotTo(BeNil()) @@ -311,7 +311,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { } Expect(repoConfig).To(Equal(testConfig)) - offlineContainer := services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer := services.GetOfflineContainer(*deploy) Expect(offlineContainer.Env).To(HaveLen(1)) assertEnvFrom(*offlineContainer) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) @@ -331,7 +331,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(err).NotTo(HaveOccurred()) Expect(repoConfigOffline).To(Equal(testConfig)) - onlineContainer := services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer := services.GetOnlineContainer(*deploy) Expect(onlineContainer.Env).To(HaveLen(3)) Expect(onlineContainer.ImagePullPolicy).To(Equal(corev1.PullAlways)) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) @@ -405,7 +405,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - registryContainer = services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer = services.GetRegistryContainer(*deploy) env = getFeatureStoreYamlEnvVar(registryContainer.Env) Expect(env).NotTo(BeNil()) fsYamlStr, err = feast.GetServiceFeatureStoreYamlBase64() @@ -422,7 +422,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(repoConfig).To(Equal(testConfig)) // check offline config - offlineContainer = services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer = services.GetRegistryContainer(*deploy) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) Expect(env).NotTo(BeNil()) @@ -438,7 +438,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(repoConfigOffline).To(Equal(testConfig)) // check online config - onlineContainer = services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer = services.GetOnlineContainer(*deploy) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) Expect(env).NotTo(BeNil()) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_kubernetes_auth_test.go b/infra/feast-operator/internal/controller/featurestore_controller_kubernetes_auth_test.go index c4c40caedc6..e3a0e8fa646 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_kubernetes_auth_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_kubernetes_auth_test.go @@ -397,7 +397,7 @@ var _ = Describe("FeatureStore Controller-Kubernetes authorization", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - env := getFeatureStoreYamlEnvVar(services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers).Env) + env := getFeatureStoreYamlEnvVar(services.GetRegistryContainer(*deploy).Env) Expect(env).NotTo(BeNil()) // check registry config @@ -421,7 +421,7 @@ var _ = Describe("FeatureStore Controller-Kubernetes authorization", func() { Expect(repoConfig).To(Equal(&testConfig)) // check offline - offlineContainer := services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer := services.GetOfflineContainer(*deploy) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) Expect(env).NotTo(BeNil()) @@ -440,7 +440,7 @@ var _ = Describe("FeatureStore Controller-Kubernetes authorization", func() { Expect(repoConfig).To(Equal(&testConfig)) // check online - onlineContainer := services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer := services.GetOnlineContainer(*deploy) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) Expect(env).NotTo(BeNil()) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_loglevel_test.go b/infra/feast-operator/internal/controller/featurestore_controller_loglevel_test.go index 5139e14dd38..f238ca9ee4c 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_loglevel_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_loglevel_test.go @@ -165,15 +165,15 @@ var _ = Describe("FeatureStore Controller - Feast service LogLevel", func() { Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - command := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers).Command + command := services.GetRegistryContainer(*deploy).Command Expect(command).To(ContainElement("--log-level")) Expect(command).To(ContainElement("ERROR")) - command = services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers).Command + command = services.GetOfflineContainer(*deploy).Command Expect(command).To(ContainElement("--log-level")) Expect(command).To(ContainElement("INFO")) - command = services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers).Command + command = services.GetOnlineContainer(*deploy).Command Expect(command).To(ContainElement("--log-level")) Expect(command).To(ContainElement("DEBUG")) }) @@ -221,13 +221,13 @@ var _ = Describe("FeatureStore Controller - Feast service LogLevel", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - command := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers).Command + command := services.GetRegistryContainer(*deploy).Command Expect(command).NotTo(ContainElement("--log-level")) - command = services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers).Command + command = services.GetOfflineContainer(*deploy).Command Expect(command).NotTo(ContainElement("--log-level")) - command = services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers).Command + command = services.GetOnlineContainer(*deploy).Command Expect(command).NotTo(ContainElement("--log-level")) }) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_objectstore_test.go b/infra/feast-operator/internal/controller/featurestore_controller_objectstore_test.go index 6b287673c4a..6e9adbf8eb5 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_objectstore_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_objectstore_test.go @@ -190,9 +190,9 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(1)) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) - Expect(services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers)).NotTo(BeNil()) - Expect(services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers)).To(BeNil()) - Expect(services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers)).To(BeNil()) + Expect(services.GetRegistryContainer(*deploy)).NotTo(BeNil()) + Expect(services.GetOnlineContainer(*deploy)).To(BeNil()) + Expect(services.GetOfflineContainer(*deploy)).To(BeNil()) Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(1)) Expect(deploy.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) @@ -226,7 +226,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(1)) - registryContainer := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.VolumeMounts).To(HaveLen(1)) }) @@ -285,9 +285,9 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) - Expect(services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers)).NotTo(BeNil()) - Expect(services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers)).To(BeNil()) - Expect(services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers)).To(BeNil()) + Expect(services.GetRegistryContainer(*deploy)).NotTo(BeNil()) + Expect(services.GetOnlineContainer(*deploy)).To(BeNil()) + Expect(services.GetOfflineContainer(*deploy)).To(BeNil()) Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(1)) Expect(deploy.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(1)) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_oidc_auth_test.go b/infra/feast-operator/internal/controller/featurestore_controller_oidc_auth_test.go index 913ab2695ed..23a4a32702b 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_oidc_auth_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_oidc_auth_test.go @@ -224,12 +224,12 @@ var _ = Describe("FeatureStore Controller-OIDC authorization", func() { Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(1)) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(1)) - Expect(services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers).VolumeMounts).To(HaveLen(1)) - Expect(services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers).VolumeMounts).To(HaveLen(1)) - Expect(services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers).VolumeMounts).To(HaveLen(1)) + Expect(services.GetOfflineContainer(*deploy).VolumeMounts).To(HaveLen(1)) + Expect(services.GetOnlineContainer(*deploy).VolumeMounts).To(HaveLen(1)) + Expect(services.GetRegistryContainer(*deploy).VolumeMounts).To(HaveLen(1)) - assertEnvFrom(*services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers)) - assertEnvFrom(*services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers)) + assertEnvFrom(*services.GetOnlineContainer(*deploy)) + assertEnvFrom(*services.GetOfflineContainer(*deploy)) // check Feast Role feastRole := &rbacv1.Role{} @@ -338,7 +338,7 @@ var _ = Describe("FeatureStore Controller-OIDC authorization", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - env := getFeatureStoreYamlEnvVar(services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers).Env) + env := getFeatureStoreYamlEnvVar(services.GetRegistryContainer(*deploy).Env) Expect(env).NotTo(BeNil()) // check registry config @@ -371,7 +371,7 @@ var _ = Describe("FeatureStore Controller-OIDC authorization", func() { Expect(repoConfig).To(Equal(testConfig)) // check offline - env = getFeatureStoreYamlEnvVar(services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers).Env) + env = getFeatureStoreYamlEnvVar(services.GetOfflineContainer(*deploy).Env) Expect(env).NotTo(BeNil()) // check offline config @@ -387,7 +387,7 @@ var _ = Describe("FeatureStore Controller-OIDC authorization", func() { Expect(repoConfig).To(Equal(testConfig)) // check online - env = getFeatureStoreYamlEnvVar(services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers).Env) + env = getFeatureStoreYamlEnvVar(services.GetOnlineContainer(*deploy).Env) Expect(env).NotTo(BeNil()) // check online config diff --git a/infra/feast-operator/internal/controller/featurestore_controller_pvc_test.go b/infra/feast-operator/internal/controller/featurestore_controller_pvc_test.go index fa40a34d955..c21f702d559 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_pvc_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_pvc_test.go @@ -264,6 +264,18 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(resource.Status.Phase).To(Equal(feastdevv1alpha1.ReadyPhase)) + ephemeralName := "feast-data" + ephemeralVolume := corev1.Volume{ + Name: ephemeralName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + } + ephemeralVolMount := corev1.VolumeMount{ + Name: ephemeralName, + MountPath: "/" + ephemeralName, + } + // check deployment deploy := &appsv1.Deployment{} objMeta := feast.GetObjectMeta() @@ -276,13 +288,15 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Volumes).NotTo(ContainElement(ephemeralVolume)) name := feast.GetFeastServiceName(services.RegistryFeastType) regVol := services.GetRegistryVolume(feast.Handler.FeatureStore, deploy.Spec.Template.Spec.Volumes) Expect(regVol.Name).To(Equal(name)) Expect(regVol.PersistentVolumeClaim.ClaimName).To(Equal(name)) - offlineContainer := services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer := services.GetOfflineContainer(*deploy) Expect(offlineContainer.VolumeMounts).To(HaveLen(3)) + Expect(offlineContainer.VolumeMounts).NotTo(ContainElement(ephemeralVolMount)) offlineVolMount := services.GetOfflineVolumeMount(feast.Handler.FeatureStore, offlineContainer.VolumeMounts) Expect(offlineVolMount.MountPath).To(Equal(offlineStoreMountPath)) offlinePvcName := feast.GetFeastServiceName(services.OfflineFeastType) @@ -308,8 +322,9 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { onlineVol := services.GetOnlineVolume(feast.Handler.FeatureStore, deploy.Spec.Template.Spec.Volumes) Expect(onlineVol.Name).To(Equal(onlinePvcName)) Expect(onlineVol.PersistentVolumeClaim.ClaimName).To(Equal(onlinePvcName)) - onlineContainer := services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer := services.GetOnlineContainer(*deploy) Expect(onlineContainer.VolumeMounts).To(HaveLen(3)) + Expect(onlineContainer.VolumeMounts).NotTo(ContainElement(ephemeralVolMount)) onlineVolMount := services.GetOnlineVolumeMount(feast.Handler.FeatureStore, onlineContainer.VolumeMounts) Expect(onlineVolMount.MountPath).To(Equal(onlineStoreMountPath)) Expect(onlineVolMount.Name).To(Equal(onlinePvcName)) @@ -334,8 +349,9 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { registryVol := services.GetRegistryVolume(feast.Handler.FeatureStore, deploy.Spec.Template.Spec.Volumes) Expect(registryVol.Name).To(Equal(registryPvcName)) Expect(registryVol.PersistentVolumeClaim.ClaimName).To(Equal(registryPvcName)) - registryContainer := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.VolumeMounts).To(HaveLen(3)) + Expect(registryContainer.VolumeMounts).NotTo(ContainElement(ephemeralVolMount)) registryVolMount := services.GetRegistryVolumeMount(feast.Handler.FeatureStore, registryContainer.VolumeMounts) Expect(registryVolMount.MountPath).To(Equal(registryMountPath)) Expect(registryVolMount.Name).To(Equal(registryPvcName)) @@ -371,15 +387,19 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { feast.Handler.FeatureStore = resource Expect(resource.Status.Applied.Services.OnlineStore.Persistence.FilePersistence.PvcConfig).To(BeNil()) - // check online deployment + // check online deployment/container deploy = &appsv1.Deployment{} err = k8sClient.Get(ctx, types.NamespacedName{ Name: objMeta.Name, Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(2)) - Expect(services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers).VolumeMounts).To(HaveLen(2)) + Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Volumes).To(ContainElement(ephemeralVolume)) + Expect(services.GetOnlineContainer(*deploy).VolumeMounts).To(HaveLen(3)) + Expect(services.GetOnlineContainer(*deploy).VolumeMounts).To(ContainElement(ephemeralVolMount)) + Expect(services.GetRegistryContainer(*deploy).VolumeMounts).To(ContainElement(ephemeralVolMount)) + Expect(services.GetOfflineContainer(*deploy).VolumeMounts).To(ContainElement(ephemeralVolMount)) // check online pvc is deleted log.FromContext(feast.Handler.Context).Info("Checking deletion of", "PersistentVolumeClaim", deploy.Name) @@ -449,7 +469,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - registryContainer := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(registryContainer.Env) Expect(env).NotTo(BeNil()) @@ -483,7 +503,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { } Expect(repoConfig).To(Equal(testConfig)) - offlineContainer := services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer := services.GetOfflineContainer(*deploy) Expect(offlineContainer.Env).To(HaveLen(1)) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) Expect(env).NotTo(BeNil()) @@ -501,7 +521,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(repoConfigOffline).To(Equal(testConfig)) // check online config - onlineContainer := services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer := services.GetOnlineContainer(*deploy) Expect(onlineContainer.Env).To(HaveLen(3)) Expect(onlineContainer.ImagePullPolicy).To(Equal(corev1.PullAlways)) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) @@ -582,7 +602,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - registryContainer = services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer = services.GetRegistryContainer(*deploy) env = getFeatureStoreYamlEnvVar(registryContainer.Env) Expect(env).NotTo(BeNil()) fsYamlStr, err = feast.GetServiceFeatureStoreYamlBase64() @@ -599,7 +619,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(repoConfig).To(Equal(testConfig)) // check offline config - offlineContainer = services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer = services.GetOfflineContainer(*deploy) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) Expect(env).NotTo(BeNil()) @@ -615,7 +635,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(repoConfigOffline).To(Equal(testConfig)) // check online config - onlineContainer = services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer = services.GetOfflineContainer(*deploy) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) Expect(env).NotTo(BeNil()) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_test.go b/infra/feast-operator/internal/controller/featurestore_controller_test.go index b4d5befe4ef..9f146bf1f61 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_test.go @@ -228,6 +228,7 @@ var _ = Describe("FeatureStore Controller", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.ServiceAccountName).To(Equal(deploy.Name)) + Expect(deploy.Spec.Strategy.Type).To(Equal(appsv1.RecreateDeploymentStrategyType)) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(deploy.Spec.Template.Spec.Containers[0].Env) @@ -267,6 +268,11 @@ var _ = Describe("FeatureStore Controller", func() { // change feast project and reconcile resourceNew := resource.DeepCopy() resourceNew.Spec.FeastProject = "changed" + resourceNew.Spec.Services = &feastdevv1alpha1.FeatureStoreServices{ + DeploymentStrategy: &appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + }, + } err = k8sClient.Update(ctx, resourceNew) Expect(err).NotTo(HaveOccurred()) _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ @@ -285,6 +291,7 @@ var _ = Describe("FeatureStore Controller", func() { Expect(err).NotTo(HaveOccurred()) testConfig.Project = resourceNew.Spec.FeastProject + Expect(deploy.Spec.Strategy.Type).To(Equal(appsv1.RollingUpdateDeploymentStrategyType)) Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(1)) env = getFeatureStoreYamlEnvVar(deploy.Spec.Template.Spec.Containers[0].Env) Expect(env).NotTo(BeNil()) @@ -606,7 +613,7 @@ var _ = Describe("FeatureStore Controller", func() { Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.ServiceAccountName).To(Equal(deploy.Name)) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - registryContainer := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(registryContainer.Env) Expect(env).NotTo(BeNil()) @@ -628,7 +635,7 @@ var _ = Describe("FeatureStore Controller", func() { // check offline config Expect(deploy.Spec.Template.Spec.ServiceAccountName).To(Equal(deploy.Name)) - offlineContainer := services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer := services.GetOfflineContainer(*deploy) Expect(offlineContainer.Env).To(HaveLen(1)) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) Expect(env).NotTo(BeNil()) @@ -647,7 +654,7 @@ var _ = Describe("FeatureStore Controller", func() { Expect(repoConfigOffline).To(Equal(&testConfig)) // check online config - onlineContainer := services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer := services.GetOnlineContainer(*deploy) Expect(onlineContainer.Env).To(HaveLen(3)) Expect(onlineContainer.ImagePullPolicy).To(Equal(corev1.PullAlways)) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) @@ -795,7 +802,7 @@ var _ = Describe("FeatureStore Controller", func() { Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.ServiceAccountName).To(Equal(deploy.Name)) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - onlineContainer := services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer := services.GetOnlineContainer(*deploy) Expect(onlineContainer.Env).To(HaveLen(3)) Expect(areEnvVarArraysEqual(onlineContainer.Env, []corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue}, {Name: services.TmpFeatureStoreYamlEnvVar, Value: fsYamlStr}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}})).To(BeTrue()) Expect(onlineContainer.ImagePullPolicy).To(Equal(corev1.PullAlways)) @@ -819,7 +826,7 @@ var _ = Describe("FeatureStore Controller", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) - onlineContainer = services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer = services.GetOnlineContainer(*deploy) Expect(onlineContainer.Env).To(HaveLen(3)) Expect(areEnvVarArraysEqual(onlineContainer.Env, []corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue + "1"}, {Name: services.TmpFeatureStoreYamlEnvVar, Value: fsYamlStr}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}}}})).To(BeTrue()) }) @@ -1033,6 +1040,26 @@ var _ = Describe("FeatureStore Controller", func() { } Expect(repoConfigClient).To(Equal(clientConfig)) + // disable init containers + resource.Spec.Services.DisableInitContainers = true + err = k8sClient.Update(ctx, resource) + Expect(err).NotTo(HaveOccurred()) + + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: nsName, + }) + Expect(err).NotTo(HaveOccurred()) + + deploy = &appsv1.Deployment{} + objMeta = feast.GetObjectMeta() + err = k8sClient.Get(ctx, types.NamespacedName{ + Name: objMeta.Name, + Namespace: objMeta.Namespace, + }, deploy) + Expect(err).NotTo(HaveOccurred()) + Expect(deploy.Spec.Template.Spec.InitContainers).To(BeEmpty()) + + // break remote reference hostname := "test:80" referencedRegistry.Spec.Services.Registry = &feastdevv1alpha1.Registry{ Remote: &feastdevv1alpha1.RemoteRegistryConfig{ diff --git a/infra/feast-operator/internal/controller/featurestore_controller_tls_test.go b/infra/feast-operator/internal/controller/featurestore_controller_tls_test.go index 0b7fe84d22a..06355c664c3 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_tls_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_tls_test.go @@ -246,7 +246,7 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - registryContainer := services.GetRegistryContainer(deploy.Spec.Template.Spec.Containers) + registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(registryContainer.Env) Expect(env).NotTo(BeNil()) @@ -267,7 +267,7 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { Expect(repoConfig).To(Equal(&testConfig)) // check offline config - offlineContainer := services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer := services.GetOfflineContainer(*deploy) Expect(offlineContainer.Env).To(HaveLen(1)) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) Expect(env).NotTo(BeNil()) @@ -284,7 +284,7 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { Expect(repoConfigOffline).To(Equal(&testConfig)) // check online config - onlineContainer := services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer := services.GetOnlineContainer(*deploy) Expect(onlineContainer.Env).To(HaveLen(1)) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) Expect(env).NotTo(BeNil()) @@ -388,7 +388,7 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(2)) // check offline config - offlineContainer = services.GetOfflineContainer(deploy.Spec.Template.Spec.Containers) + offlineContainer = services.GetOfflineContainer(*deploy) env = getFeatureStoreYamlEnvVar(offlineContainer.Env) Expect(env).NotTo(BeNil()) @@ -410,7 +410,7 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { Expect(repoConfigOffline).To(Equal(&testConfig)) // check online config - onlineContainer = services.GetOnlineContainer(deploy.Spec.Template.Spec.Containers) + onlineContainer = services.GetOnlineContainer(*deploy) env = getFeatureStoreYamlEnvVar(onlineContainer.Env) Expect(env).NotTo(BeNil()) 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 9138f00f2f6..1e5909b8892 100644 --- a/infra/feast-operator/internal/controller/services/repo_config_test.go +++ b/infra/feast-operator/internal/controller/services/repo_config_test.go @@ -55,12 +55,13 @@ var _ = Describe("Repo Config", func() { By("Having the local registry resource") featureStore = minimalFeatureStore() + testPath := "/test/file.db" featureStore.Spec.Services = &feastdevv1alpha1.FeatureStoreServices{ Registry: &feastdevv1alpha1.Registry{ Local: &feastdevv1alpha1.LocalRegistryConfig{ Persistence: &feastdevv1alpha1.RegistryPersistence{ FilePersistence: &feastdevv1alpha1.RegistryFilePersistence{ - Path: "file.db", + Path: testPath, }, }, }, @@ -70,7 +71,7 @@ var _ = Describe("Repo Config", func() { expectedRegistryConfig = RegistryConfig{ RegistryType: "file", - Path: "file.db", + Path: testPath, } repoConfig, err = getServiceRepoConfig(featureStore, emptyMockExtractConfigFromSecret) @@ -80,53 +81,46 @@ var _ = Describe("Repo Config", func() { Expect(repoConfig.OnlineStore).To(Equal(expectedOnlineConfig)) Expect(repoConfig.Registry).To(Equal(expectedRegistryConfig)) - By("Having the remote registry resource") - featureStore = minimalFeatureStore() - featureStore.Spec.Services = &feastdevv1alpha1.FeatureStoreServices{ - Registry: &feastdevv1alpha1.Registry{ - Remote: &feastdevv1alpha1.RemoteRegistryConfig{ - FeastRef: &feastdevv1alpha1.FeatureStoreRef{ - Name: "registry", + By("Adding an offlineStore with PVC") + featureStore.Spec.Services.OfflineStore = &feastdevv1alpha1.OfflineStore{ + Persistence: &feastdevv1alpha1.OfflineStorePersistence{ + FilePersistence: &feastdevv1alpha1.OfflineStoreFilePersistence{ + PvcConfig: &feastdevv1alpha1.PvcConfig{ + MountPath: "/testing", }, }, }, } ApplyDefaultsToStatus(featureStore) + appliedServices := featureStore.Status.Applied.Services + Expect(appliedServices.OnlineStore).To(BeNil()) + Expect(appliedServices.OnlineStore).To(BeNil()) + repoConfig, err = getServiceRepoConfig(featureStore, emptyMockExtractConfigFromSecret) Expect(err).NotTo(HaveOccurred()) + Expect(repoConfig.OfflineStore).To(Equal(defaultOfflineStoreConfig)) Expect(repoConfig.AuthzConfig.Type).To(Equal(NoAuthAuthType)) - Expect(repoConfig.OfflineStore).To(Equal(emptyOfflineStoreConfig)) + Expect(repoConfig.Registry).To(Equal(expectedRegistryConfig)) Expect(repoConfig.OnlineStore).To(Equal(expectedOnlineConfig)) - Expect(repoConfig.Registry).To(Equal(emptyRegistryConfig)) - - By("Having an offlineStore with PVC") - mountPath := "/testing" - expectedOnlineConfig.Path = mountPath + "/" + DefaultOnlineStorePath - expectedRegistryConfig.Path = mountPath + "/" + DefaultRegistryPath + By("Having the remote registry resource") featureStore = minimalFeatureStore() featureStore.Spec.Services = &feastdevv1alpha1.FeatureStoreServices{ - OfflineStore: &feastdevv1alpha1.OfflineStore{ - Persistence: &feastdevv1alpha1.OfflineStorePersistence{ - FilePersistence: &feastdevv1alpha1.OfflineStoreFilePersistence{ - PvcConfig: &feastdevv1alpha1.PvcConfig{ - MountPath: mountPath, - }, + Registry: &feastdevv1alpha1.Registry{ + Remote: &feastdevv1alpha1.RemoteRegistryConfig{ + FeastRef: &feastdevv1alpha1.FeatureStoreRef{ + Name: "registry", }, }, }, } ApplyDefaultsToStatus(featureStore) - appliedServices := featureStore.Status.Applied.Services - Expect(appliedServices.OnlineStore).To(BeNil()) - Expect(appliedServices.Registry.Local.Persistence.FilePersistence.Path).To(Equal(expectedRegistryConfig.Path)) - repoConfig, err = getServiceRepoConfig(featureStore, emptyMockExtractConfigFromSecret) Expect(err).NotTo(HaveOccurred()) - Expect(repoConfig.OfflineStore).To(Equal(defaultOfflineStoreConfig)) Expect(repoConfig.AuthzConfig.Type).To(Equal(NoAuthAuthType)) - Expect(repoConfig.Registry).To(Equal(expectedRegistryConfig)) + Expect(repoConfig.OfflineStore).To(Equal(emptyOfflineStoreConfig)) Expect(repoConfig.OnlineStore).To(Equal(expectedOnlineConfig)) + Expect(repoConfig.Registry).To(Equal(emptyRegistryConfig)) By("Having the all the file services") featureStore = minimalFeatureStore() diff --git a/infra/feast-operator/internal/controller/services/services.go b/infra/feast-operator/internal/controller/services/services.go index 16f3e663902..1c8a7d26e53 100644 --- a/infra/feast-operator/internal/controller/services/services.go +++ b/infra/feast-operator/internal/controller/services/services.go @@ -49,6 +49,9 @@ func (feast *FeastServices) ApplyDefaults() error { // Deploy the feast services func (feast *FeastServices) Deploy() error { + if feast.noLocalServiceConfigured() { + return errors.New("At least one local service must be configured. e.g. registry / online / offline.") + } openshiftTls, err := feast.checkOpenshiftTls() if err != nil { return err @@ -278,9 +281,7 @@ func (feast *FeastServices) setDeployment(deploy *appsv1.Deployment) error { deploy.Spec = appsv1.DeploymentSpec{ Replicas: &DefaultReplicas, Selector: metav1.SetAsLabelSelector(deploy.GetLabels()), - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.RecreateDeploymentStrategyType, - }, + Strategy: feast.getDeploymentStrategy(), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: deploy.GetLabels(), @@ -313,24 +314,9 @@ func (feast *FeastServices) setContainers(podSpec *corev1.PodSpec) error { if err != nil { return err } - feastProject := feast.Handler.FeatureStore.Status.Applied.FeastProject - workingDir := getOfflineMountPath(feast.Handler.FeatureStore) - podSpec.InitContainers = append(podSpec.InitContainers, corev1.Container{ - Name: "feast-init", - Image: DefaultImage, - Env: []corev1.EnvVar{ - { - Name: TmpFeatureStoreYamlEnvVar, - Value: fsYamlB64, - }, - }, - Command: []string{"/bin/sh", "-c"}, - Args: []string{"echo \"Starting feast initialization job...\";\n[ -d " + - feastProject + " ] || feast init " + feastProject + ";\necho $" + - TmpFeatureStoreYamlEnvVar + " | base64 -d \u003e " + workingDir + "/" + feastProject + - "/feature_repo/feature_store.yaml;\necho \"Feast initialization complete\";\n"}, - WorkingDir: workingDir, - }) + + feast.setInitContainer(podSpec, fsYamlB64) + if feast.isLocalRegistry() { feast.setContainer(&podSpec.Containers, RegistryFeastType, fsYamlB64) } @@ -351,7 +337,7 @@ func (feast *FeastServices) setContainer(containers *[]corev1.Container, feastTy container := &corev1.Container{ Name: string(feastType), Image: *defaultServiceConfigs.Image, - WorkingDir: getOfflineMountPath(feast.Handler.FeatureStore) + "/" + feast.Handler.FeatureStore.Status.Applied.FeastProject + "/feature_repo", + WorkingDir: getOfflineMountPath(feast.Handler.FeatureStore) + "/" + feast.Handler.FeatureStore.Status.Applied.FeastProject + FeatureRepoDir, Command: feast.getContainerCommand(feastType), Ports: []corev1.ContainerPort{ { @@ -365,12 +351,6 @@ func (feast *FeastServices) setContainer(containers *[]corev1.Container, feastTy Name: TmpFeatureStoreYamlEnvVar, Value: fsYamlB64, }, - /* - { - Name: mlpConfigVar, - Value: DefaultMlpConfigPath, - }, - */ }, StartupProbe: &corev1.Probe{ ProbeHandler: probeHandler, @@ -417,26 +397,62 @@ func (feast *FeastServices) getContainerCommand(feastType FeastServiceType) []st return feastCommand } -func (feast *FeastServices) setRegistryClientInitContainer(podSpec *corev1.PodSpec) { - hostname := feast.Handler.FeatureStore.Status.ServiceHostnames.Registry - // add grpc init container if remote registry reference (feastRef) is configured - if len(hostname) > 0 && feast.IsRemoteRefRegistry() { - grpcurlFlag := "-plaintext" - hostSplit := strings.Split(hostname, ":") - if len(hostSplit) > 1 && hostSplit[1] == "443" { - grpcurlFlag = "-insecure" - } +func (feast *FeastServices) getDeploymentStrategy() appsv1.DeploymentStrategy { + if feast.Handler.FeatureStore.Status.Applied.Services.DeploymentStrategy != nil { + return *feast.Handler.FeatureStore.Status.Applied.Services.DeploymentStrategy + } + return appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + } +} + +func (feast *FeastServices) setInitContainer(podSpec *corev1.PodSpec, fsYamlB64 string) { + if !feast.Handler.FeatureStore.Status.Applied.Services.DisableInitContainers { + feastProject := feast.Handler.FeatureStore.Status.Applied.FeastProject + feastRepoDir := feastProject + FeatureRepoDir + workingDir := getOfflineMountPath(feast.Handler.FeatureStore) podSpec.InitContainers = append(podSpec.InitContainers, corev1.Container{ - Name: "init-registry", - Image: "fullstorydev/grpcurl:v1.9.1-alpine", - Command: []string{ - "sh", "-c", - "until grpcurl -H \"authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\" " + - grpcurlFlag + " -d '' -format text " + hostname + " grpc.health.v1.Health/Check; do echo waiting for registry; sleep 2; done", + Name: "feast-init", + Image: getFeatureServerImage(), + Env: []corev1.EnvVar{ + { + Name: TmpFeatureStoreYamlEnvVar, + Value: fsYamlB64, + }, }, + Command: []string{"/bin/sh", "-c"}, + Args: []string{"echo \"Starting feast initialization job...\";\n[ -d " + + feastRepoDir + " ] || feast init " + feastProject + ";\necho $" + + TmpFeatureStoreYamlEnvVar + " | base64 -d \u003e " + workingDir + "/" + feastRepoDir + + "/feature_store.yaml;\necho \"Feast initialization complete\";\n"}, + WorkingDir: workingDir, }) } } + +// add grpc init container if remote registry reference (feastRef) is configured +func (feast *FeastServices) setRegistryClientInitContainer(podSpec *corev1.PodSpec) { + if !feast.Handler.FeatureStore.Status.Applied.Services.DisableInitContainers { + hostname := feast.Handler.FeatureStore.Status.ServiceHostnames.Registry + if len(hostname) > 0 && feast.IsRemoteRefRegistry() { + grpcurlFlag := "-plaintext" + hostSplit := strings.Split(hostname, ":") + if len(hostSplit) > 1 && hostSplit[1] == "443" { + grpcurlFlag = "-insecure" + } + podSpec.InitContainers = append(podSpec.InitContainers, corev1.Container{ + Name: "init-registry", + Image: getGrpcCurlImage(), + Command: []string{ + "sh", "-c", + "until grpcurl -H \"authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\" " + + grpcurlFlag + " -d '' -format text " + hostname + " grpc.health.v1.Health/Check; do echo waiting for registry; sleep 2; done", + }, + }) + } + } +} + func (feast *FeastServices) setService(svc *corev1.Service, feastType FeastServiceType) error { svc.Labels = feast.getFeastTypeLabels(feastType) if feast.isOpenShiftTls(feastType) { @@ -632,6 +648,9 @@ func (feast *FeastServices) getRemoteRegistryFeastHandler() (*FeastServices, err } return nil, err } + if feast.Handler.FeatureStore.Status.Applied.FeastProject != remoteFeastObj.Status.Applied.FeastProject { + return nil, errors.New("FeatureStore '" + remoteFeastObj.Name + "' is using a different feast project than '" + feast.Handler.FeatureStore.Status.Applied.FeastProject + "'. Project names must match.") + } return &FeastServices{ Handler: handler.FeastHandler{ Client: feast.Handler.Client, @@ -672,6 +691,10 @@ func (feast *FeastServices) isOnlinStore() bool { return appliedServices != nil && appliedServices.OnlineStore != nil } +func (feast *FeastServices) noLocalServiceConfigured() bool { + return !(feast.isLocalRegistry() || feast.isOnlinStore() || feast.isOfflinStore()) +} + func (feast *FeastServices) initFeastDeploy() *appsv1.Deployment { deploy := &appsv1.Deployment{ ObjectMeta: feast.GetObjectMeta(), diff --git a/infra/feast-operator/internal/controller/services/services_types.go b/infra/feast-operator/internal/controller/services/services_types.go index 882839a429f..55a5a42e2a7 100644 --- a/infra/feast-operator/internal/controller/services/services_types.go +++ b/infra/feast-operator/internal/controller/services/services_types.go @@ -28,6 +28,7 @@ const ( TmpFeatureStoreYamlEnvVar = "TMP_FEATURE_STORE_YAML_BASE64" FeatureStoreYamlCmKey = "feature_store.yaml" EphemeralPath = "/feast-data" + FeatureRepoDir = "/feature_repo" DefaultRegistryPath = "registry.db" DefaultOnlineStorePath = "online_store.db" svcDomain = ".svc.cluster.local" diff --git a/infra/feast-operator/internal/controller/services/tls.go b/infra/feast-operator/internal/controller/services/tls.go index 6dcca7edea1..c1b07e7e1e6 100644 --- a/infra/feast-operator/internal/controller/services/tls.go +++ b/infra/feast-operator/internal/controller/services/tls.go @@ -190,7 +190,7 @@ func (feast *FeastServices) mountTlsConfig(feastType FeastServiceType, podSpec * }, }, }) - if i, container := getContainerByType(feastType, podSpec.Containers); container != nil { + if i, container := getContainerByType(feastType, *podSpec); container != nil { podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{ Name: volName, MountPath: GetTlsPath(feastType), diff --git a/infra/feast-operator/internal/controller/services/tls_test.go b/infra/feast-operator/internal/controller/services/tls_test.go index 5baeb381d7f..6c964084204 100644 --- a/infra/feast-operator/internal/controller/services/tls_test.go +++ b/infra/feast-operator/internal/controller/services/tls_test.go @@ -254,15 +254,15 @@ var _ = Describe("TLS Config", func() { err = feast.setDeployment(feastDeploy) Expect(err).ToNot(HaveOccurred()) Expect(feastDeploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - Expect(GetOfflineContainer(feastDeploy.Spec.Template.Spec.Containers)).NotTo(BeNil()) + Expect(GetOfflineContainer(*feastDeploy)).NotTo(BeNil()) Expect(feastDeploy.Spec.Template.Spec.Volumes).To(HaveLen(2)) - Expect(GetRegistryContainer(feastDeploy.Spec.Template.Spec.Containers).Command).NotTo(ContainElements(ContainSubstring("--key"))) - Expect(GetRegistryContainer(feastDeploy.Spec.Template.Spec.Containers).VolumeMounts).To(HaveLen(1)) - Expect(GetOfflineContainer(feastDeploy.Spec.Template.Spec.Containers).Command).To(ContainElements(ContainSubstring("--key"))) - Expect(GetOfflineContainer(feastDeploy.Spec.Template.Spec.Containers).VolumeMounts).To(HaveLen(2)) - Expect(GetOnlineContainer(feastDeploy.Spec.Template.Spec.Containers).Command).NotTo(ContainElements(ContainSubstring("--key"))) - Expect(GetOnlineContainer(feastDeploy.Spec.Template.Spec.Containers).VolumeMounts).To(HaveLen(1)) + Expect(GetRegistryContainer(*feastDeploy).Command).NotTo(ContainElements(ContainSubstring("--key"))) + Expect(GetRegistryContainer(*feastDeploy).VolumeMounts).To(HaveLen(1)) + Expect(GetOfflineContainer(*feastDeploy).Command).To(ContainElements(ContainSubstring("--key"))) + Expect(GetOfflineContainer(*feastDeploy).VolumeMounts).To(HaveLen(2)) + Expect(GetOnlineContainer(*feastDeploy).Command).NotTo(ContainElements(ContainSubstring("--key"))) + Expect(GetOnlineContainer(*feastDeploy).VolumeMounts).To(HaveLen(1)) }) }) }) diff --git a/infra/feast-operator/internal/controller/services/util.go b/infra/feast-operator/internal/controller/services/util.go index 7b9c177c89d..b39a851c14b 100644 --- a/infra/feast-operator/internal/controller/services/util.go +++ b/infra/feast-operator/internal/controller/services/util.go @@ -2,12 +2,14 @@ package services import ( "fmt" + "os" "reflect" "slices" "strings" "github.com/feast-dev/feast/infra/feast-operator/api/feastversion" feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -64,8 +66,12 @@ func shouldCreatePvc(featureStore *feastdevv1alpha1.FeatureStore, feastType Feas } func shouldMountEmptyDir(featureStore *feastdevv1alpha1.FeatureStore) bool { - _, ok := hasPvcConfig(featureStore, OfflineFeastType) - return !ok + for _, feastType := range feastServerTypes { + if _, ok := hasPvcConfig(featureStore, feastType); !ok { + return true + } + } + return false } func getOfflineMountPath(featureStore *feastdevv1alpha1.FeatureStore) string { @@ -160,8 +166,23 @@ func ApplyDefaultsToStatus(cr *feastdevv1alpha1.FeatureStore) { func setServiceDefaultConfigs(defaultConfigs *feastdevv1alpha1.DefaultConfigs) { if defaultConfigs.Image == nil { - defaultConfigs.Image = &DefaultImage + img := getFeatureServerImage() + defaultConfigs.Image = &img + } +} + +func getFeatureServerImage() string { + if img, exists := os.LookupEnv("RELATED_IMAGE_FEATURE_SERVER"); exists { + return img } + return DefaultImage +} + +func getGrpcCurlImage() string { + if img, exists := os.LookupEnv("RELATED_IMAGE_GRPC_CURL"); exists { + return img + } + return "fullstorydev/grpcurl:v1.9.1-alpine" } func checkOfflineStoreFilePersistenceType(value string) error { @@ -204,16 +225,16 @@ func defaultOnlineStorePath(featureStore *feastdevv1alpha1.FeatureStore) string if _, ok := hasPvcConfig(featureStore, OnlineFeastType); ok { return DefaultOnlineStorePath } - // if online pvc not set, use offline's mount path. - return getOfflineMountPath(featureStore) + "/" + DefaultOnlineStorePath + // if pvc not set, use the ephemeral mount path. + return EphemeralPath + "/" + DefaultOnlineStorePath } func defaultRegistryPath(featureStore *feastdevv1alpha1.FeatureStore) string { if _, ok := hasPvcConfig(featureStore, RegistryFeastType); ok { return DefaultRegistryPath } - // if registry pvc not set, use offline's mount path. - return getOfflineMountPath(featureStore) + "/" + DefaultRegistryPath + // if pvc not set, use the ephemeral mount path. + return EphemeralPath + "/" + DefaultRegistryPath } func checkOfflineStoreDBStorePersistenceType(value string) error { @@ -238,15 +259,14 @@ func checkRegistryDBStorePersistenceType(value string) error { } func (feast *FeastServices) getSecret(secretRef string) (*corev1.Secret, error) { + logger := log.FromContext(feast.Handler.Context) secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: secretRef, Namespace: feast.Handler.FeatureStore.Namespace}} objectKey := client.ObjectKeyFromObject(secret) if err := feast.Handler.Client.Get(feast.Handler.Context, objectKey, secret); err != nil { - if apierrors.IsNotFound(err) || err != nil { - logger := log.FromContext(feast.Handler.Context) + if apierrors.IsNotFound(err) { logger.Error(err, "invalid secret "+secretRef+" for offline store") - - return nil, err } + return nil, err } return secret, nil @@ -366,23 +386,23 @@ func envOverride(dst, src []corev1.EnvVar) []corev1.EnvVar { return dst } -func GetRegistryContainer(containers []corev1.Container) *corev1.Container { - _, container := getContainerByType(RegistryFeastType, containers) +func GetRegistryContainer(deployment appsv1.Deployment) *corev1.Container { + _, container := getContainerByType(RegistryFeastType, deployment.Spec.Template.Spec) return container } -func GetOfflineContainer(containers []corev1.Container) *corev1.Container { - _, container := getContainerByType(OfflineFeastType, containers) +func GetOfflineContainer(deployment appsv1.Deployment) *corev1.Container { + _, container := getContainerByType(OfflineFeastType, deployment.Spec.Template.Spec) return container } -func GetOnlineContainer(containers []corev1.Container) *corev1.Container { - _, container := getContainerByType(OnlineFeastType, containers) +func GetOnlineContainer(deployment appsv1.Deployment) *corev1.Container { + _, container := getContainerByType(OnlineFeastType, deployment.Spec.Template.Spec) return container } -func getContainerByType(feastType FeastServiceType, containers []corev1.Container) (int, *corev1.Container) { - for i, c := range containers { +func getContainerByType(feastType FeastServiceType, podSpec corev1.PodSpec) (int, *corev1.Container) { + for i, c := range podSpec.Containers { if c.Name == string(feastType) { return i, &c } diff --git a/infra/scripts/release/files_to_bump.txt b/infra/scripts/release/files_to_bump.txt index 8dabe1104f5..0e0ac0bce6d 100644 --- a/infra/scripts/release/files_to_bump.txt +++ b/infra/scripts/release/files_to_bump.txt @@ -14,6 +14,7 @@ infra/feast-helm-operator/Makefile 6 infra/feast-helm-operator/config/manager/kustomization.yaml 8 infra/feast-operator/Makefile 6 infra/feast-operator/config/manager/kustomization.yaml 8 +infra/feast-operator/config/default/manager_related_images_patch.yaml 5 infra/feast-operator/config/overlays/odh/params.env 1 infra/feast-operator/api/feastversion/version.go 20 java/pom.xml 38