From 1b9458df89c7482ad66d3f777c4f754cc2610d08 Mon Sep 17 00:00:00 2001 From: Abdul Hameed Date: Fri, 10 Jan 2025 11:31:12 -0500 Subject: [PATCH] feat: Add Feast Operator component Signed-off-by: Abdul Hameed --- README.md | 2 + .../v1alpha1/feastoperator_types.go | 110 ++++++++++++ .../v1alpha1/zz_generated.deepcopy.go | 161 ++++++++++++++++++ .../v1/datasciencecluster_types.go | 6 + .../v1/zz_generated.deepcopy.go | 2 + ...latform.opendatahub.io_feastoperators.yaml | 159 +++++++++++++++++ ...er.opendatahub.io_datascienceclusters.yaml | 64 +++++++ ...atahub-operator.clusterserviceversion.yaml | 14 +- ...latform.opendatahub.io_feastoperators.yaml | 153 +++++++++++++++++ ...er.opendatahub.io_datascienceclusters.yaml | 64 +++++++ config/crd/kustomization.yaml | 1 + ...atahub-operator.clusterserviceversion.yaml | 3 +- config/rbac/role.yaml | 3 + ...asciencecluster_v1_datasciencecluster.yaml | 2 + .../components/feastoperator/feastoperator.go | 107 ++++++++++++ .../feastoperator/feastoperator_controller.go | 60 +++++++ .../feastoperator_controller_actions.go | 39 +++++ .../feastoperator/feastoperator_support.go | 38 +++++ .../datasciencecluster_controller.go | 1 + .../datasciencecluster/kubebuilder_rbac.go | 5 + controllers/webhook/webhook_suite_test.go | 5 + docs/DESIGN.md | 4 +- docs/api-overview.md | 144 ++++++++++++++++ docs/upgrade-testing.md | 3 + get_all_manifests.sh | 1 + main.go | 1 + pkg/cluster/gvk/gvk.go | 6 + pkg/upgrade/upgrade.go | 3 + tests/e2e/controller_test.go | 1 + tests/e2e/feastoperator_test.go | 29 ++++ tests/e2e/helper_test.go | 5 + tests/e2e/odh_manager_test.go | 6 + 32 files changed, 1198 insertions(+), 4 deletions(-) create mode 100644 apis/components/v1alpha1/feastoperator_types.go create mode 100644 bundle/manifests/components.platform.opendatahub.io_feastoperators.yaml create mode 100644 config/crd/bases/components.platform.opendatahub.io_feastoperators.yaml create mode 100644 controllers/components/feastoperator/feastoperator.go create mode 100644 controllers/components/feastoperator/feastoperator_controller.go create mode 100644 controllers/components/feastoperator/feastoperator_controller_actions.go create mode 100644 controllers/components/feastoperator/feastoperator_support.go create mode 100644 tests/e2e/feastoperator_test.go diff --git a/README.md b/README.md index 73be7feeb0e..423a928b590 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,8 @@ spec: managementState: Managed workbenches: managementState: Managed + feastoperator: + managementState: Managed ``` 2. Enable only Dashboard and Workbenches diff --git a/apis/components/v1alpha1/feastoperator_types.go b/apis/components/v1alpha1/feastoperator_types.go new file mode 100644 index 00000000000..92db669c6d6 --- /dev/null +++ b/apis/components/v1alpha1/feastoperator_types.go @@ -0,0 +1,110 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "github.com/opendatahub-io/opendatahub-operator/v2/apis/common" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // FeastOperatorName is the name of the new component + FeastOperatorComponentName = "feastoperator" + + // FeastOperatorInstanceName is the singleton name for the FeastOperator instance + // Value must match the validation rule defined below + FeastOperatorInstanceName = "default-" + FeastOperatorComponentName + + // FeastOperatorKind represents the Kubernetes kind for FeastOperator + FeastOperatorKind = "FeastOperator" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'default-feastoperator'",message="FeastOperator name must be 'default-feastoperator'" +// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`,description="Ready" +// +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`,description="Reason" + +// FeastOperator is the Schema for the FeastOperator API +type FeastOperator struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FeastOperatorSpec `json:"spec,omitempty"` + Status FeastOperatorStatus `json:"status,omitempty"` +} + +// FeastOperatorCommonSpec defines the common spec shared across APIs for FeastOperator +type FeastOperatorCommonSpec struct { + // Spec fields exposed to the DSC API + common.DevFlagsSpec `json:",inline"` +} + +// FeastOperatorCommonStatus defines the shared observed state of FeastOperator +type FeastOperatorCommonStatus struct { +} + +// FeastOperatorSpec defines the desired state of FeastOperator +type FeastOperatorSpec struct { + FeastOperatorCommonSpec `json:",inline"` +} + +// FeastOperatorStatus defines the observed state of FeastOperator +type FeastOperatorStatus struct { + common.Status `json:",inline"` + FeastOperatorCommonStatus `json:",inline"` +} + +// GetDevFlags retrieves the development flags from the spec +func (c *FeastOperator) GetDevFlags() *common.DevFlags { + return c.Spec.DevFlags +} + +// GetStatus retrieves the status of the FeastOperator component +func (c *FeastOperator) GetStatus() *common.Status { + return &c.Status.Status +} + +// +kubebuilder:object:root=true + +// FeastOperatorList contains a list of FeastOperator objects +type FeastOperatorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []FeastOperator `json:"items"` +} + +// DSCFeastOperator defines the configuration exposed in the DSC instance for FeastOperator +type DSCFeastOperator struct { + // Fields common across components + common.ManagementSpec `json:",inline"` + + // FeastOperator-specific fields + FeastOperatorCommonSpec `json:",inline"` +} + +// DSCFeastOperatorStatus struct holds the status for the FeastOperator component exposed in the DSC +type DSCFeastOperatorStatus struct { + common.ManagementSpec `json:",inline"` + *FeastOperatorCommonStatus `json:",inline"` +} + +func init() { + // Register the schema with the scheme builder + SchemeBuilder.Register(&FeastOperator{}, &FeastOperatorList{}) +} diff --git a/apis/components/v1alpha1/zz_generated.deepcopy.go b/apis/components/v1alpha1/zz_generated.deepcopy.go index 9de0b610abc..b88eff6bed0 100644 --- a/apis/components/v1alpha1/zz_generated.deepcopy.go +++ b/apis/components/v1alpha1/zz_generated.deepcopy.go @@ -261,6 +261,44 @@ func (in *DSCDataSciencePipelinesStatus) DeepCopy() *DSCDataSciencePipelinesStat return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DSCFeastOperator) DeepCopyInto(out *DSCFeastOperator) { + *out = *in + out.ManagementSpec = in.ManagementSpec + in.FeastOperatorCommonSpec.DeepCopyInto(&out.FeastOperatorCommonSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DSCFeastOperator. +func (in *DSCFeastOperator) DeepCopy() *DSCFeastOperator { + if in == nil { + return nil + } + out := new(DSCFeastOperator) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DSCFeastOperatorStatus) DeepCopyInto(out *DSCFeastOperatorStatus) { + *out = *in + out.ManagementSpec = in.ManagementSpec + if in.FeastOperatorCommonStatus != nil { + in, out := &in.FeastOperatorCommonStatus, &out.FeastOperatorCommonStatus + *out = new(FeastOperatorCommonStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DSCFeastOperatorStatus. +func (in *DSCFeastOperatorStatus) DeepCopy() *DSCFeastOperatorStatus { + if in == nil { + return nil + } + out := new(DSCFeastOperatorStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DSCKserve) DeepCopyInto(out *DSCKserve) { *out = *in @@ -811,6 +849,129 @@ func (in *DataSciencePipelinesStatus) DeepCopy() *DataSciencePipelinesStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FeastOperator) DeepCopyInto(out *FeastOperator) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeastOperator. +func (in *FeastOperator) DeepCopy() *FeastOperator { + if in == nil { + return nil + } + out := new(FeastOperator) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FeastOperator) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FeastOperatorCommonSpec) DeepCopyInto(out *FeastOperatorCommonSpec) { + *out = *in + in.DevFlagsSpec.DeepCopyInto(&out.DevFlagsSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeastOperatorCommonSpec. +func (in *FeastOperatorCommonSpec) DeepCopy() *FeastOperatorCommonSpec { + if in == nil { + return nil + } + out := new(FeastOperatorCommonSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FeastOperatorCommonStatus) DeepCopyInto(out *FeastOperatorCommonStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeastOperatorCommonStatus. +func (in *FeastOperatorCommonStatus) DeepCopy() *FeastOperatorCommonStatus { + if in == nil { + return nil + } + out := new(FeastOperatorCommonStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FeastOperatorList) DeepCopyInto(out *FeastOperatorList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FeastOperator, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeastOperatorList. +func (in *FeastOperatorList) DeepCopy() *FeastOperatorList { + if in == nil { + return nil + } + out := new(FeastOperatorList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FeastOperatorList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FeastOperatorSpec) DeepCopyInto(out *FeastOperatorSpec) { + *out = *in + in.FeastOperatorCommonSpec.DeepCopyInto(&out.FeastOperatorCommonSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeastOperatorSpec. +func (in *FeastOperatorSpec) DeepCopy() *FeastOperatorSpec { + if in == nil { + return nil + } + out := new(FeastOperatorSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FeastOperatorStatus) DeepCopyInto(out *FeastOperatorStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + out.FeastOperatorCommonStatus = in.FeastOperatorCommonStatus +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeastOperatorStatus. +func (in *FeastOperatorStatus) DeepCopy() *FeastOperatorStatus { + if in == nil { + return nil + } + out := new(FeastOperatorStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Kserve) DeepCopyInto(out *Kserve) { *out = *in diff --git a/apis/datasciencecluster/v1/datasciencecluster_types.go b/apis/datasciencecluster/v1/datasciencecluster_types.go index fd5956758a2..5a002237b2a 100644 --- a/apis/datasciencecluster/v1/datasciencecluster_types.go +++ b/apis/datasciencecluster/v1/datasciencecluster_types.go @@ -69,6 +69,9 @@ type Components struct { // Training Operator component configuration. TrainingOperator componentApi.DSCTrainingOperator `json:"trainingoperator,omitempty"` + + // Feast Operator component configuration. + FeastOperator componentApi.DSCFeastOperator `json:"feastoperator,omitempty"` } // ComponentsStatus defines the custom status of DataScienceCluster components. @@ -105,6 +108,9 @@ type ComponentsStatus struct { // Training Operator component status. TrainingOperator componentApi.DSCTrainingOperatorStatus `json:"trainingoperator,omitempty"` + + // Feast Operator component status. + FeastOperator componentApi.DSCFeastOperatorStatus `json:"feastoperator,omitempty"` } // DataScienceClusterStatus defines the observed state of DataScienceCluster. diff --git a/apis/datasciencecluster/v1/zz_generated.deepcopy.go b/apis/datasciencecluster/v1/zz_generated.deepcopy.go index 1d797ff3283..a7c9cfe5f33 100644 --- a/apis/datasciencecluster/v1/zz_generated.deepcopy.go +++ b/apis/datasciencecluster/v1/zz_generated.deepcopy.go @@ -40,6 +40,7 @@ func (in *Components) DeepCopyInto(out *Components) { in.TrustyAI.DeepCopyInto(&out.TrustyAI) in.ModelRegistry.DeepCopyInto(&out.ModelRegistry) in.TrainingOperator.DeepCopyInto(&out.TrainingOperator) + in.FeastOperator.DeepCopyInto(&out.FeastOperator) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Components. @@ -66,6 +67,7 @@ func (in *ComponentsStatus) DeepCopyInto(out *ComponentsStatus) { in.TrustyAI.DeepCopyInto(&out.TrustyAI) in.ModelRegistry.DeepCopyInto(&out.ModelRegistry) in.TrainingOperator.DeepCopyInto(&out.TrainingOperator) + in.FeastOperator.DeepCopyInto(&out.FeastOperator) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentsStatus. diff --git a/bundle/manifests/components.platform.opendatahub.io_feastoperators.yaml b/bundle/manifests/components.platform.opendatahub.io_feastoperators.yaml new file mode 100644 index 00000000000..d8465d6bac2 --- /dev/null +++ b/bundle/manifests/components.platform.opendatahub.io_feastoperators.yaml @@ -0,0 +1,159 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + creationTimestamp: null + name: feastoperators.components.platform.opendatahub.io +spec: + group: components.platform.opendatahub.io + names: + kind: FeastOperator + listKind: FeastOperatorList + plural: feastoperators + singular: feastoperator + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Ready + jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - description: Reason + jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Reason + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: FeastOperator is the Schema for the FeastOperator API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: FeastOperatorSpec defines the desired state of FeastOperator + properties: + devFlags: + description: Add developer fields + properties: + manifests: + description: List of custom manifests for the given component + items: + properties: + contextDir: + default: manifests + description: contextDir is the relative path to the folder + containing manifests in a repository, default value "manifests" + type: string + sourcePath: + default: "" + description: 'sourcePath is the subpath within contextDir + where kustomize builds start. Examples include any sub-folder + or path: `base`, `overlays/dev`, `default`, `odh` etc.' + type: string + uri: + default: "" + description: uri is the URI point to a git repo with tag/branch. + e.g. https://github.com/org/repo/tarball/ + type: string + type: object + type: array + type: object + type: object + status: + description: FeastOperatorStatus defines the observed state of FeastOperator + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + format: int64 + type: integer + phase: + type: string + type: object + type: object + x-kubernetes-validations: + - message: FeastOperator name must be 'default-feastoperator' + rule: self.metadata.name == 'default-feastoperator' + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/datasciencecluster.opendatahub.io_datascienceclusters.yaml b/bundle/manifests/datasciencecluster.opendatahub.io_datascienceclusters.yaml index 7ec8e179cd6..5a5f7499c7c 100644 --- a/bundle/manifests/datasciencecluster.opendatahub.io_datascienceclusters.yaml +++ b/bundle/manifests/datasciencecluster.opendatahub.io_datascienceclusters.yaml @@ -187,6 +187,52 @@ spec: pattern: ^(Managed|Unmanaged|Force|Removed)$ type: string type: object + feastoperator: + description: Feast Operator component configuration. + properties: + devFlags: + description: Add developer fields + properties: + manifests: + description: List of custom manifests for the given component + items: + properties: + contextDir: + default: manifests + description: contextDir is the relative path to + the folder containing manifests in a repository, + default value "manifests" + type: string + sourcePath: + default: "" + description: 'sourcePath is the subpath within contextDir + where kustomize builds start. Examples include + any sub-folder or path: `base`, `overlays/dev`, + `default`, `odh` etc.' + type: string + uri: + default: "" + description: uri is the URI point to a git repo + with tag/branch. e.g. https://github.com/org/repo/tarball/ + type: string + type: object + type: array + type: object + managementState: + description: |- + Set to one of the following values: + + - "Managed" : the operator is actively managing the component and trying to keep it active. + It will only upgrade the component if it is safe to do so + + - "Removed" : the operator is actively managing the component and will not install it, + or if it is installed, the operator will try to remove it + enum: + - Managed + - Removed + pattern: ^(Managed|Unmanaged|Force|Removed)$ + type: string + type: object kserve: description: |- Kserve component configuration. @@ -715,6 +761,24 @@ spec: pattern: ^(Managed|Unmanaged|Force|Removed)$ type: string type: object + feastoperator: + description: Feast Operator component status. + properties: + managementState: + description: |- + Set to one of the following values: + + - "Managed" : the operator is actively managing the component and trying to keep it active. + It will only upgrade the component if it is safe to do so + + - "Removed" : the operator is actively managing the component and will not install it, + or if it is installed, the operator will try to remove it + enum: + - Managed + - Removed + pattern: ^(Managed|Unmanaged|Force|Removed)$ + type: string + type: object kserve: description: Kserve component status. properties: diff --git a/bundle/manifests/opendatahub-operator.clusterserviceversion.yaml b/bundle/manifests/opendatahub-operator.clusterserviceversion.yaml index 0c131ce3d56..11e190b4319 100644 --- a/bundle/manifests/opendatahub-operator.clusterserviceversion.yaml +++ b/bundle/manifests/opendatahub-operator.clusterserviceversion.yaml @@ -28,6 +28,9 @@ metadata: "datasciencepipelines": { "managementState": "Managed" }, + "feastoperator": { + "managementState": "Removed" + }, "kserve": { "managementState": "Managed", "nim": { @@ -106,7 +109,7 @@ metadata: categories: AI/Machine Learning, Big Data certified: "False" containerImage: quay.io/opendatahub/opendatahub-operator:v2.23.1 - createdAt: "2025-01-27T10:42:46Z" + createdAt: "2025-01-23T10:07:37Z" olm.skipRange: '>=1.0.0 <2.23.1' operators.operatorframework.io/builder: operator-sdk-v1.31.0 operators.operatorframework.io/internal-objects: '["featuretrackers.features.opendatahub.io", @@ -115,7 +118,8 @@ metadata: "kueues.components.platform.opendatahub.io", "modelmeshservings.components.platform.opendatahub.io", "modelregistries.components.platform.opendatahub.io", "rays.components.platform.opendatahub.io", "trainingoperators.components.platform.opendatahub.io", "trustyais.components.platform.opendatahub.io", "workbenches.components.platform.opendatahub.io", - "monitorings.services.platform.opendatahub.io","modelcontrollers.components.platform.opendatahub.io"]' + "monitorings.services.platform.opendatahub.io","modelcontrollers.components.platform.opendatahub.io" + , feastoperators.components.platform.opendatahub.io]' operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 repository: https://github.com/opendatahub-io/opendatahub-operator name: opendatahub-operator.v2.23.1 @@ -189,6 +193,9 @@ spec: displayName: Conditions path: conditions version: v1 + - kind: FeastOperator + name: feastoperators.components.platform.opendatahub.io + version: v1alpha1 - kind: FeatureTracker name: featuretrackers.features.opendatahub.io version: v1 @@ -453,6 +460,7 @@ spec: - codeflares - dashboards - datasciencepipelines + - feastoperators - kserves - kueues - modelcontrollers @@ -475,6 +483,7 @@ spec: resources: - codeflares/finalizers - datasciencepipelines/finalizers + - feastoperators/finalizers - kserves/finalizers - kueues/finalizers - modelcontrollers/finalizers @@ -492,6 +501,7 @@ spec: - codeflares/status - dashboards/status - datasciencepipelines/status + - feastoperators/status - kserves/status - kueues/status - modelcontrollers/status diff --git a/config/crd/bases/components.platform.opendatahub.io_feastoperators.yaml b/config/crd/bases/components.platform.opendatahub.io_feastoperators.yaml new file mode 100644 index 00000000000..e69024bbdd7 --- /dev/null +++ b/config/crd/bases/components.platform.opendatahub.io_feastoperators.yaml @@ -0,0 +1,153 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: feastoperators.components.platform.opendatahub.io +spec: + group: components.platform.opendatahub.io + names: + kind: FeastOperator + listKind: FeastOperatorList + plural: feastoperators + singular: feastoperator + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Ready + jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - description: Reason + jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Reason + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: FeastOperator is the Schema for the FeastOperator API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: FeastOperatorSpec defines the desired state of FeastOperator + properties: + devFlags: + description: Add developer fields + properties: + manifests: + description: List of custom manifests for the given component + items: + properties: + contextDir: + default: manifests + description: contextDir is the relative path to the folder + containing manifests in a repository, default value "manifests" + type: string + sourcePath: + default: "" + description: 'sourcePath is the subpath within contextDir + where kustomize builds start. Examples include any sub-folder + or path: `base`, `overlays/dev`, `default`, `odh` etc.' + type: string + uri: + default: "" + description: uri is the URI point to a git repo with tag/branch. + e.g. https://github.com/org/repo/tarball/ + type: string + type: object + type: array + type: object + type: object + status: + description: FeastOperatorStatus defines the observed state of FeastOperator + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + format: int64 + type: integer + phase: + type: string + type: object + type: object + x-kubernetes-validations: + - message: FeastOperator name must be 'default-feastoperator' + rule: self.metadata.name == 'default-feastoperator' + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml b/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml index 6a1c89dea45..44ab31d6e55 100644 --- a/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml +++ b/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml @@ -187,6 +187,52 @@ spec: pattern: ^(Managed|Unmanaged|Force|Removed)$ type: string type: object + feastoperator: + description: Feast Operator component configuration. + properties: + devFlags: + description: Add developer fields + properties: + manifests: + description: List of custom manifests for the given component + items: + properties: + contextDir: + default: manifests + description: contextDir is the relative path to + the folder containing manifests in a repository, + default value "manifests" + type: string + sourcePath: + default: "" + description: 'sourcePath is the subpath within contextDir + where kustomize builds start. Examples include + any sub-folder or path: `base`, `overlays/dev`, + `default`, `odh` etc.' + type: string + uri: + default: "" + description: uri is the URI point to a git repo + with tag/branch. e.g. https://github.com/org/repo/tarball/ + type: string + type: object + type: array + type: object + managementState: + description: |- + Set to one of the following values: + + - "Managed" : the operator is actively managing the component and trying to keep it active. + It will only upgrade the component if it is safe to do so + + - "Removed" : the operator is actively managing the component and will not install it, + or if it is installed, the operator will try to remove it + enum: + - Managed + - Removed + pattern: ^(Managed|Unmanaged|Force|Removed)$ + type: string + type: object kserve: description: |- Kserve component configuration. @@ -715,6 +761,24 @@ spec: pattern: ^(Managed|Unmanaged|Force|Removed)$ type: string type: object + feastoperator: + description: Feast Operator component status. + properties: + managementState: + description: |- + Set to one of the following values: + + - "Managed" : the operator is actively managing the component and trying to keep it active. + It will only upgrade the component if it is safe to do so + + - "Removed" : the operator is actively managing the component and will not install it, + or if it is installed, the operator will try to remove it + enum: + - Managed + - Removed + pattern: ^(Managed|Unmanaged|Force|Removed)$ + type: string + type: object kserve: description: Kserve component status. properties: diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index b13754a12a2..6d1e2bc3a3a 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -19,6 +19,7 @@ resources: - bases/components.platform.opendatahub.io_trainingoperators.yaml - bases/services.platform.opendatahub.io_monitorings.yaml - bases/services.platform.opendatahub.io_auths.yaml +- bases/components.platform.opendatahub.io_feastoperators.yaml #+kubebuilder:scaffold:crdkustomizeresource # patches: diff --git a/config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml b/config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml index 148967aa4cc..ec913fffd15 100644 --- a/config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml @@ -15,7 +15,8 @@ metadata: "kueues.components.platform.opendatahub.io", "modelmeshservings.components.platform.opendatahub.io", "modelregistries.components.platform.opendatahub.io", "rays.components.platform.opendatahub.io", "trainingoperators.components.platform.opendatahub.io", "trustyais.components.platform.opendatahub.io", "workbenches.components.platform.opendatahub.io", - "monitorings.services.platform.opendatahub.io","modelcontrollers.components.platform.opendatahub.io"]' + "monitorings.services.platform.opendatahub.io","modelcontrollers.components.platform.opendatahub.io" + , feastoperators.components.platform.opendatahub.io]' repository: https://github.com/opendatahub-io/opendatahub-operator name: opendatahub-operator.v2.23.1 namespace: placeholder diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ab38459732d..a7b259f1b3c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -188,6 +188,7 @@ rules: - codeflares - dashboards - datasciencepipelines + - feastoperators - kserves - kueues - modelcontrollers @@ -210,6 +211,7 @@ rules: resources: - codeflares/finalizers - datasciencepipelines/finalizers + - feastoperators/finalizers - kserves/finalizers - kueues/finalizers - modelcontrollers/finalizers @@ -227,6 +229,7 @@ rules: - codeflares/status - dashboards/status - datasciencepipelines/status + - feastoperators/status - kserves/status - kueues/status - modelcontrollers/status diff --git a/config/samples/datasciencecluster_v1_datasciencecluster.yaml b/config/samples/datasciencecluster_v1_datasciencecluster.yaml index 4dc9443c956..79ba1b7d7bb 100644 --- a/config/samples/datasciencecluster_v1_datasciencecluster.yaml +++ b/config/samples/datasciencecluster_v1_datasciencecluster.yaml @@ -46,3 +46,5 @@ spec: modelregistry: managementState: "Managed" registriesNamespace: "odh-model-registries" + feastoperator: + managementState: "Removed" diff --git a/controllers/components/feastoperator/feastoperator.go b/controllers/components/feastoperator/feastoperator.go new file mode 100644 index 00000000000..0bc5296c05d --- /dev/null +++ b/controllers/components/feastoperator/feastoperator.go @@ -0,0 +1,107 @@ +package feastoperator + +import ( + "errors" + "fmt" + + operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/opendatahub-io/opendatahub-operator/v2/apis/common" + componentApi "github.com/opendatahub-io/opendatahub-operator/v2/apis/components/v1alpha1" + dscv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/datasciencecluster/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" + cr "github.com/opendatahub-io/opendatahub-operator/v2/pkg/componentsregistry" + odhdeploy "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/metadata/annotations" +) + +type componentHandler struct{} + +func init() { //nolint:gochecknoinits + cr.Add(&componentHandler{}) +} + +func (s *componentHandler) GetName() string { + return componentApi.FeastOperatorComponentName +} + +func (s *componentHandler) GetManagementState(dsc *dscv1.DataScienceCluster) operatorv1.ManagementState { + if dsc.Spec.Components.FeastOperator.ManagementState == operatorv1.Managed { + return operatorv1.Managed + } + return operatorv1.Removed +} + +func (s *componentHandler) NewCRObject(dsc *dscv1.DataScienceCluster) common.PlatformObject { + return &componentApi.FeastOperator{ + TypeMeta: metav1.TypeMeta{ + Kind: componentApi.FeastOperatorKind, + APIVersion: componentApi.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: componentApi.FeastOperatorInstanceName, + Annotations: map[string]string{ + annotations.ManagementStateAnnotation: string(s.GetManagementState(dsc)), + }, + }, + Spec: componentApi.FeastOperatorSpec{ + FeastOperatorCommonSpec: dsc.Spec.Components.FeastOperator.FeastOperatorCommonSpec, + }, + } +} + +func (s *componentHandler) Init(platform cluster.Platform) error { + if err := odhdeploy.ApplyParams(manifestPath().String(), imageParamMap); err != nil { + return fmt.Errorf("failed to update images on path %s: %w", manifestPath(), err) + } + + return nil +} + +func (s *componentHandler) UpdateDSCStatus(dsc *dscv1.DataScienceCluster, obj client.Object) error { + c, ok := obj.(*componentApi.FeastOperator) + if !ok { + return errors.New("failed to convert to FeastOperator") + } + + dsc.Status.InstalledComponents[LegacyComponentName] = false + dsc.Status.Components.FeastOperator.ManagementSpec.ManagementState = s.GetManagementState(dsc) + dsc.Status.Components.FeastOperator.FeastOperatorCommonStatus = nil + + nc := conditionsv1.Condition{ + Type: ReadyConditionType, + Status: corev1.ConditionFalse, + Reason: "Unknown", + Message: "Not Available", + } + + switch s.GetManagementState(dsc) { + case operatorv1.Managed: + dsc.Status.InstalledComponents[LegacyComponentName] = true + dsc.Status.Components.FeastOperator.FeastOperatorCommonStatus = c.Status.FeastOperatorCommonStatus.DeepCopy() + + if rc := meta.FindStatusCondition(c.Status.Conditions, status.ConditionTypeReady); rc != nil { + nc.Status = corev1.ConditionStatus(rc.Status) + nc.Reason = rc.Reason + nc.Message = rc.Message + } + + case operatorv1.Removed: + nc.Status = corev1.ConditionFalse + nc.Reason = string(operatorv1.Removed) + nc.Message = "Component ManagementState is set to " + string(operatorv1.Removed) + + default: + return fmt.Errorf("unknown state %s ", s.GetManagementState(dsc)) + } + + conditionsv1.SetStatusCondition(&dsc.Status.Conditions, nc) + + return nil +} diff --git a/controllers/components/feastoperator/feastoperator_controller.go b/controllers/components/feastoperator/feastoperator_controller.go new file mode 100644 index 00000000000..432d5bfd82d --- /dev/null +++ b/controllers/components/feastoperator/feastoperator_controller.go @@ -0,0 +1,60 @@ +package feastoperator + +import ( + "context" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + ctrl "sigs.k8s.io/controller-runtime" + + componentApi "github.com/opendatahub-io/opendatahub-operator/v2/apis/components/v1alpha1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/actions/deploy" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/actions/gc" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/actions/render/kustomize" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/actions/updatestatus" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/handlers" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/predicates/component" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/predicates/resources" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/reconciler" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/metadata/labels" +) + +func (s *componentHandler) NewComponentReconciler(ctx context.Context, mgr ctrl.Manager) error { + _, err := reconciler.ReconcilerFor(mgr, &componentApi.FeastOperator{}). + // customized Owns() for Component with new predicates + Owns(&corev1.ConfigMap{}). + Owns(&rbacv1.RoleBinding{}). + Owns(&rbacv1.Role{}). + Owns(&corev1.ServiceAccount{}). + Owns(&appsv1.Deployment{}, reconciler.WithPredicates(resources.NewDeploymentPredicate())). + Watches( + &extv1.CustomResourceDefinition{}, + reconciler.WithEventHandler( + handlers.ToNamed(componentApi.FeastOperatorInstanceName)), + reconciler.WithPredicates( + component.ForLabel(labels.ODH.Component(LegacyComponentName), labels.True)), + ). + // Add FeastOperator-specific actions + WithAction(initialize). + WithAction(devFlags). + WithAction(kustomize.NewAction( + kustomize.WithCache(), + kustomize.WithLabel(labels.ODH.Component(LegacyComponentName), labels.True), + kustomize.WithLabel(labels.K8SCommon.PartOf, LegacyComponentName), + )). + WithAction(deploy.NewAction( + deploy.WithCache(), + )). + WithAction(updatestatus.NewAction()). + // must be the final action + WithAction(gc.NewAction()). + Build(ctx) + + if err != nil { + return err // no need customize error, it is done in the caller main + } + + return nil +} diff --git a/controllers/components/feastoperator/feastoperator_controller_actions.go b/controllers/components/feastoperator/feastoperator_controller_actions.go new file mode 100644 index 00000000000..c4cf8ef8c31 --- /dev/null +++ b/controllers/components/feastoperator/feastoperator_controller_actions.go @@ -0,0 +1,39 @@ +package feastoperator + +import ( + "context" + "fmt" + + componentApi "github.com/opendatahub-io/opendatahub-operator/v2/apis/components/v1alpha1" + odhtypes "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types" + odhdeploy "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" +) + +func initialize(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { + rr.Manifests = append(rr.Manifests, manifestPath()) + return nil +} + +func devFlags(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { + feastoperator, ok := rr.Instance.(*componentApi.FeastOperator) + if !ok { + return fmt.Errorf("resource instance %v is not a componentApi.FeastOperator)", rr.Instance) + } + + if feastoperator.Spec.DevFlags == nil { + return nil + } + if len(feastoperator.Spec.DevFlags.Manifests) != 0 { + manifestConfig := feastoperator.Spec.DevFlags.Manifests[0] + if err := odhdeploy.DownloadManifests(ctx, ComponentName, manifestConfig); err != nil { + return err + } + if manifestConfig.SourcePath != "" { + rr.Manifests[0].Path = odhdeploy.DefaultManifestPath + rr.Manifests[0].ContextDir = ComponentName + rr.Manifests[0].SourcePath = manifestConfig.SourcePath + } + } + // TODO: Implement devflags logmode logic + return nil +} diff --git a/controllers/components/feastoperator/feastoperator_support.go b/controllers/components/feastoperator/feastoperator_support.go new file mode 100644 index 00000000000..a54a3ea9534 --- /dev/null +++ b/controllers/components/feastoperator/feastoperator_support.go @@ -0,0 +1,38 @@ +package feastoperator + +import ( + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" + + componentApi "github.com/opendatahub-io/opendatahub-operator/v2/apis/components/v1alpha1" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types" + odhdeploy "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" +) + +const ( + ComponentName = componentApi.FeastOperatorComponentName + + ReadyConditionType = conditionsv1.ConditionType(componentApi.FeastOperatorKind + status.ReadySuffix) + + // LegacyComponentName is the name of the component that is assigned to deployments + // via Kustomize. Since a deployment selector is immutable, we can't upgrade existing + // deployment to the new component name, so keep it around till we figure out a solution. + LegacyComponentName = "feastoperator" + + ManifestsSourcePath = "overlays/odh" +) + +var ( + imageParamMap = map[string]string{ + "odh-feast-operator-controller-image": "RELATED_IMAGE_ODH_FEAST_OPERATOR_IMAGE", + "odh-feast-feature-server-image": "RELATED_IMAGE_ODH_FEAST_FEATURE_SERVER_IMAGE", + } +) + +func manifestPath() types.ManifestInfo { + return types.ManifestInfo{ + Path: odhdeploy.DefaultManifestPath, + ContextDir: ComponentName, + SourcePath: ManifestsSourcePath, + } +} diff --git a/controllers/datasciencecluster/datasciencecluster_controller.go b/controllers/datasciencecluster/datasciencecluster_controller.go index d306ccb9a05..c25c0859a22 100644 --- a/controllers/datasciencecluster/datasciencecluster_controller.go +++ b/controllers/datasciencecluster/datasciencecluster_controller.go @@ -294,6 +294,7 @@ func (r *DataScienceClusterReconciler) SetupWithManager(_ context.Context, mgr c Owns(&componentApi.Kserve{}, builder.WithPredicates(componentsPredicate)). Owns(&componentApi.ModelMeshServing{}, builder.WithPredicates(componentsPredicate)). Owns(&componentApi.ModelController{}, builder.WithPredicates(componentsPredicate)). + Owns(&componentApi.FeastOperator{}, builder.WithPredicates(componentsPredicate)). // others Watches( &dsciv1.DSCInitialization{}, diff --git a/controllers/datasciencecluster/kubebuilder_rbac.go b/controllers/datasciencecluster/kubebuilder_rbac.go index 296f62021f2..4311a9cd733 100644 --- a/controllers/datasciencecluster/kubebuilder_rbac.go +++ b/controllers/datasciencecluster/kubebuilder_rbac.go @@ -238,3 +238,8 @@ package datasciencecluster // +kubebuilder:rbac:groups=services.platform.opendatahub.io,resources=auths,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=services.platform.opendatahub.io,resources=auths/status,verbs=get;update;patch // +kubebuilder:rbac:groups=services.platform.opendatahub.io,resources=auths/finalizers,verbs=update + +// FeastOperator +// +kubebuilder:rbac:groups=components.platform.opendatahub.io,resources=feastoperators,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=components.platform.opendatahub.io,resources=feastoperators/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=components.platform.opendatahub.io,resources=feastoperators/finalizers,verbs=update diff --git a/controllers/webhook/webhook_suite_test.go b/controllers/webhook/webhook_suite_test.go index 757e17f7ddd..04ac0841b82 100644 --- a/controllers/webhook/webhook_suite_test.go +++ b/controllers/webhook/webhook_suite_test.go @@ -303,6 +303,11 @@ func newDSC(name string, namespace string) *dscv1.DataScienceCluster { ManagementState: operatorv1.Removed, }, }, + FeastOperator: componentApi.DSCFeastOperator{ + ManagementSpec: common.ManagementSpec{ + ManagementState: operatorv1.Removed, + }, + }, }, }, } diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 148517713b8..b7936572dad 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -75,7 +75,9 @@ To deploy ODH components seamlessly, ODH operator will watch two CRDs: managementState: Managed trustyai: managementState: Managed - ``` + feastoperator: + managementState: Managed + ``` 2. Enable only Dashboard and Workbenches(Jupyter Notebooks) diff --git a/docs/api-overview.md b/docs/api-overview.md index b791e5b9788..bd3d57ec89f 100644 --- a/docs/api-overview.md +++ b/docs/api-overview.md @@ -18,6 +18,8 @@ Package v1 contains API Schema definitions for the components v1 API group - [DashboardList](#dashboardlist) - [DataSciencePipelines](#datasciencepipelines) - [DataSciencePipelinesList](#datasciencepipelineslist) +- [FeastOperator](#feastoperator) +- [FeastOperatorList](#feastoperatorlist) - [Kserve](#kserve) - [KserveList](#kservelist) - [Kueue](#kueue) @@ -237,6 +239,39 @@ DSCDataSciencePipelinesStatus contains the observed state of the DataSciencePipe +_Appears in:_ +- [ComponentsStatus](#componentsstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `managementState` _[ManagementState](#managementstate)_ | Set to one of the following values:

- "Managed" : the operator is actively managing the component and trying to keep it active.
It will only upgrade the component if it is safe to do so

- "Removed" : the operator is actively managing the component and will not install it,
or if it is installed, the operator will try to remove it | | Enum: [Managed Removed]
| + + +#### DSCFeastOperator + + + +DSCFeastOperator defines the configuration exposed in the DSC instance for FeastOperator + + + +_Appears in:_ +- [Components](#components) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `managementState` _[ManagementState](#managementstate)_ | Set to one of the following values:

- "Managed" : the operator is actively managing the component and trying to keep it active.
It will only upgrade the component if it is safe to do so

- "Removed" : the operator is actively managing the component and will not install it,
or if it is installed, the operator will try to remove it | | Enum: [Managed Removed]
| +| `devFlags` _[DevFlags](#devflags)_ | Add developer fields | | | + + +#### DSCFeastOperatorStatus + + + +DSCFeastOperatorStatus struct holds the status for the FeastOperator component exposed in the DSC + + + _Appears in:_ - [ComponentsStatus](#componentsstatus) @@ -751,6 +786,113 @@ _Appears in:_ | `RawDeployment` | RawDeployment will be used as the default deployment mode for Kserve.
| +#### FeastOperator + + + +FeastOperator is the Schema for the FeastOperator API + + + +_Appears in:_ +- [FeastOperatorList](#feastoperatorlist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `components.platform.opendatahub.io/v1alpha1` | | | +| `kind` _string_ | `FeastOperator` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[FeastOperatorSpec](#feastoperatorspec)_ | | | | +| `status` _[FeastOperatorStatus](#feastoperatorstatus)_ | | | | + + +#### FeastOperatorCommonSpec + + + +FeastOperatorCommonSpec defines the common spec shared across APIs for FeastOperator + + + +_Appears in:_ +- [DSCFeastOperator](#dscfeastoperator) +- [FeastOperatorSpec](#feastoperatorspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `devFlags` _[DevFlags](#devflags)_ | Add developer fields | | | + + +#### FeastOperatorCommonStatus + + + +FeastOperatorCommonStatus defines the shared observed state of FeastOperator + + + +_Appears in:_ +- [DSCFeastOperatorStatus](#dscfeastoperatorstatus) +- [FeastOperatorStatus](#feastoperatorstatus) + + + +#### FeastOperatorList + + + +FeastOperatorList contains a list of FeastOperator objects + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `components.platform.opendatahub.io/v1alpha1` | | | +| `kind` _string_ | `FeastOperatorList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `items` _[FeastOperator](#feastoperator) array_ | | | | + + +#### FeastOperatorSpec + + + +FeastOperatorSpec defines the desired state of FeastOperator + + + +_Appears in:_ +- [FeastOperator](#feastoperator) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `devFlags` _[DevFlags](#devflags)_ | Add developer fields | | | + + +#### FeastOperatorStatus + + + +FeastOperatorStatus defines the observed state of FeastOperator + + + +_Appears in:_ +- [FeastOperator](#feastoperator) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _string_ | | | | +| `observedGeneration` _integer_ | | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#condition-v1-meta) array_ | | | | + + #### Kserve @@ -1848,6 +1990,7 @@ _Appears in:_ | `trustyai` _[DSCTrustyAI](#dsctrustyai)_ | TrustyAI component configuration. | | | | `modelregistry` _[DSCModelRegistry](#dscmodelregistry)_ | ModelRegistry component configuration. | | | | `trainingoperator` _[DSCTrainingOperator](#dsctrainingoperator)_ | Training Operator component configuration. | | | +| `feastoperator` _[DSCFeastOperator](#dscfeastoperator)_ | Feast Operator component configuration. | | | #### ComponentsStatus @@ -1874,6 +2017,7 @@ _Appears in:_ | `trustyai` _[DSCTrustyAIStatus](#dsctrustyaistatus)_ | TrustyAI component status. | | | | `modelregistry` _[DSCModelRegistryStatus](#dscmodelregistrystatus)_ | ModelRegistry component status. | | | | `trainingoperator` _[DSCTrainingOperatorStatus](#dsctrainingoperatorstatus)_ | Training Operator component status. | | | +| `feastoperator` _[DSCFeastOperatorStatus](#dscfeastoperatorstatus)_ | Feast Operator component status. | | | #### ControlPlaneSpec diff --git a/docs/upgrade-testing.md b/docs/upgrade-testing.md index d40d38f9418..70436b38e2c 100644 --- a/docs/upgrade-testing.md +++ b/docs/upgrade-testing.md @@ -149,5 +149,8 @@ spec: managementState: Managed trustyai: managementState: Managed + feastoperator: + managementState: Managed + EOF ``` \ No newline at end of file diff --git a/get_all_manifests.sh b/get_all_manifests.sh index 3995b03be6b..7dfdb3010ff 100755 --- a/get_all_manifests.sh +++ b/get_all_manifests.sh @@ -20,6 +20,7 @@ declare -A COMPONENT_MANIFESTS=( ["trainingoperator"]="opendatahub-io:training-operator:dev:manifests" ["datasciencepipelines"]="opendatahub-io:data-science-pipelines-operator:main:config" ["modelcontroller"]="opendatahub-io:odh-model-controller:incubating:config" + ["feastoperator"]="opendatahub-io:feast:stable:infra/feast-operator/config" ) # Allow overwriting repo using flags component=repo diff --git a/main.go b/main.go index 44b240f7d91..a593e6cc41e 100644 --- a/main.go +++ b/main.go @@ -86,6 +86,7 @@ import ( _ "github.com/opendatahub-io/opendatahub-operator/v2/controllers/components/codeflare" _ "github.com/opendatahub-io/opendatahub-operator/v2/controllers/components/dashboard" _ "github.com/opendatahub-io/opendatahub-operator/v2/controllers/components/datasciencepipelines" + _ "github.com/opendatahub-io/opendatahub-operator/v2/controllers/components/feastoperator" _ "github.com/opendatahub-io/opendatahub-operator/v2/controllers/components/kserve" _ "github.com/opendatahub-io/opendatahub-operator/v2/controllers/components/kueue" _ "github.com/opendatahub-io/opendatahub-operator/v2/controllers/components/modelcontroller" diff --git a/pkg/cluster/gvk/gvk.go b/pkg/cluster/gvk/gvk.go index 240e4408e38..27c67731781 100644 --- a/pkg/cluster/gvk/gvk.go +++ b/pkg/cluster/gvk/gvk.go @@ -185,6 +185,12 @@ var ( Kind: serviceApi.MonitoringKind, } + FeastOperator = schema.GroupVersionKind{ + Group: componentApi.GroupVersion.Group, + Version: componentApi.GroupVersion.Version, + Kind: componentApi.FeastOperatorKind, + } + CustomResourceDefinition = schema.GroupVersionKind{ Group: "apiextensions.k8s.io", Version: "v1", diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go index 0ca00cbb6fb..de1f7337d7c 100644 --- a/pkg/upgrade/upgrade.go +++ b/pkg/upgrade/upgrade.go @@ -93,6 +93,9 @@ func CreateDefaultDSC(ctx context.Context, cli client.Client) error { TrainingOperator: componentApi.DSCTrainingOperator{ ManagementSpec: common.ManagementSpec{ManagementState: operatorv1.Managed}, }, + FeastOperator: componentApi.DSCFeastOperator{ + ManagementSpec: common.ManagementSpec{ManagementState: operatorv1.Managed}, + }, }, }, } diff --git a/tests/e2e/controller_test.go b/tests/e2e/controller_test.go index 887847b995e..d06d98546a2 100644 --- a/tests/e2e/controller_test.go +++ b/tests/e2e/controller_test.go @@ -55,6 +55,7 @@ var ( componentApi.KserveComponentName: kserveTestSuite, componentApi.ModelMeshServingComponentName: modelMeshServingTestSuite, componentApi.ModelControllerComponentName: modelControllerTestSuite, + componentApi.FeastOperatorComponentName: feastOperatorTestSuite, } servicesTestSuites = map[string]TestFn{ diff --git a/tests/e2e/feastoperator_test.go b/tests/e2e/feastoperator_test.go new file mode 100644 index 00000000000..18251eb7478 --- /dev/null +++ b/tests/e2e/feastoperator_test.go @@ -0,0 +1,29 @@ +package e2e_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + componentApi "github.com/opendatahub-io/opendatahub-operator/v2/apis/components/v1alpha1" +) + +func feastOperatorTestSuite(t *testing.T) { + t.Helper() + + ct, err := NewComponentTestCtx(&componentApi.FeastOperator{}) + require.NoError(t, err) + + componentCtx := FeastOperatorTestCtx{ + ComponentTestCtx: ct, + } + + t.Run("Validate component enabled", componentCtx.ValidateComponentEnabled) + t.Run("Validate operands have OwnerReferences", componentCtx.ValidateOperandsOwnerReferences) + t.Run("Validate update operand resources", componentCtx.ValidateUpdateDeploymentsResources) + t.Run("Validate component disabled", componentCtx.ValidateComponentDisabled) +} + +type FeastOperatorTestCtx struct { + *ComponentTestCtx +} diff --git a/tests/e2e/helper_test.go b/tests/e2e/helper_test.go index e838e0bf6a8..ed9c05fde46 100644 --- a/tests/e2e/helper_test.go +++ b/tests/e2e/helper_test.go @@ -208,6 +208,11 @@ func setupDSCInstance(name string) *dscv1.DataScienceCluster { ManagementState: operatorv1.Removed, }, }, + FeastOperator: componentApi.DSCFeastOperator{ + ManagementSpec: common.ManagementSpec{ + ManagementState: operatorv1.Removed, + }, + }, }, }, } diff --git a/tests/e2e/odh_manager_test.go b/tests/e2e/odh_manager_test.go index 083c51cd713..9a3c858b6e9 100644 --- a/tests/e2e/odh_manager_test.go +++ b/tests/e2e/odh_manager_test.go @@ -75,6 +75,12 @@ func (tc *testContext) validateOwnedCRDs(t *testing.T) { "error in validating CRD : trainingoperators.components.platform.opendatahub.io") }) + t.Run("Validate FeastOperator CRD", func(t *testing.T) { + t.Parallel() + require.NoErrorf(t, tc.validateCRD("feastoperators.components.platform.opendatahub.io"), + "error in validating CRD : feastoperators.components.platform.opendatahub.io") + }) + t.Run("Validate DataSciencePipelines CRD", func(t *testing.T) { t.Parallel() require.NoErrorf(t, tc.validateCRD("datasciencepipelines.components.platform.opendatahub.io"),