diff --git a/api/v1alpha1/dscinitialization_types.go b/api/v1alpha1/dscinitialization_types.go index b8d776c018e..ee6c4463adb 100644 --- a/api/v1alpha1/dscinitialization_types.go +++ b/api/v1alpha1/dscinitialization_types.go @@ -22,6 +22,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// List of constants to show different different reconciliation messages and statuses. +const ( + ReconcileFailed = "ReconcileFailed" + ReconcileInit = "ReconcileInit" + ReconcileCompleted = "ReconcileCompleted" + ReconcileCompletedMessage = "Reconcile completed successfully" +) + // DSCInitializationSpec defines the desired state of DSCInitiatlization type DSCInitializationSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster @@ -57,8 +65,8 @@ type DSCInitiatlizationStatus struct { //+kubebuilder:printcolumn:name="Created At",type=string,JSONPath=.metadata.creationTimestamp //+operator-sdk:csv:customresourcedefinitions:displayName="DSC Initialization" -// DSCInitiatlization is the Schema for the dscinitiatlizations API -type DSCInitiatlization struct { +// DSCInitialization is the Schema for the dscinitiatlizations API +type DSCInitialization struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -68,13 +76,13 @@ type DSCInitiatlization struct { //+kubebuilder:object:root=true -// DSCInitiatlizationList contains a list of DSCInitiatlization -type DSCInitiatlizationList struct { +// DSCInitializationList contains a list of DSCInitiatlization +type DSCInitializationList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` - Items []DSCInitiatlization `json:"items"` + Items []DSCInitialization `json:"items"` } func init() { - SchemeBuilder.Register(&DSCInitiatlization{}, &DSCInitiatlizationList{}) + SchemeBuilder.Register(&DSCInitialization{}, &DSCInitializationList{}) } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 1815cb696bc..00d4d45ed0b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -43,7 +43,7 @@ func (in *DSCInitializationSpec) DeepCopy() *DSCInitializationSpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DSCInitiatlization) DeepCopyInto(out *DSCInitiatlization) { +func (in *DSCInitialization) DeepCopyInto(out *DSCInitialization) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -52,17 +52,17 @@ func (in *DSCInitiatlization) DeepCopyInto(out *DSCInitiatlization) { } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DSCInitiatlization. -func (in *DSCInitiatlization) DeepCopy() *DSCInitiatlization { +func (in *DSCInitialization) DeepCopy() *DSCInitialization { if in == nil { return nil } - out := new(DSCInitiatlization) + out := new(DSCInitialization) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DSCInitiatlization) DeepCopyObject() runtime.Object { +func (in *DSCInitialization) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -70,13 +70,13 @@ func (in *DSCInitiatlization) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DSCInitiatlizationList) DeepCopyInto(out *DSCInitiatlizationList) { +func (in *DSCInitializationList) DeepCopyInto(out *DSCInitializationList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]DSCInitiatlization, len(*in)) + *out = make([]DSCInitialization, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -84,17 +84,17 @@ func (in *DSCInitiatlizationList) DeepCopyInto(out *DSCInitiatlizationList) { } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DSCInitiatlizationList. -func (in *DSCInitiatlizationList) DeepCopy() *DSCInitiatlizationList { +func (in *DSCInitializationList) DeepCopy() *DSCInitializationList { if in == nil { return nil } - out := new(DSCInitiatlizationList) + out := new(DSCInitializationList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DSCInitiatlizationList) DeepCopyObject() runtime.Object { +func (in *DSCInitializationList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } diff --git a/controllers/dscinitiatlization_controller.go b/controllers/dscinitiatlization_controller.go index 6c1f56f96e4..633435ff780 100644 --- a/controllers/dscinitiatlization_controller.go +++ b/controllers/dscinitiatlization_controller.go @@ -20,17 +20,21 @@ import ( "context" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" - dscinitializationv1alpha1 "github.com/opendatahub-io/opendatahub-operator/api/v1alpha1" + "github.com/go-logr/logr" + dsci "github.com/opendatahub-io/opendatahub-operator/api/v1alpha1" ) -// DSCInitiatlizationReconciler reconciles a DSCInitiatlization object -type DSCInitiatlizationReconciler struct { +// DSCInitializationReconciler reconciles a DSCInitiatlization object +type DSCInitializationReconciler struct { client.Client Scheme *runtime.Scheme + ctx context.Context + Log logr.Logger } //+kubebuilder:rbac:groups=dscinitialization.opendatahub.io,resources=dscinitiatlizations,verbs=get;list;watch;create;update;patch;delete @@ -46,17 +50,49 @@ type DSCInitiatlizationReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile -func (r *DSCInitiatlizationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) +func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - // TODO(user): your logic here + //_ = log.FromContext(ctx) + prevLogger := r.Log + defer func() { r.Log = prevLogger }() + r.Log = r.Log.WithValues("Request.Namespace", req.Namespace, "Request.Name", req.Name) + r.ctx = ctx + + r.Log.Info("Reconciling OCSInitialization.", "OCSInitialization", klog.KRef(req.Namespace, req.Name)) + + instance := &dsci.DSCInitialization{} + err := r.Client.Get(ctx, req.NamespacedName, instance) + + // Start reconciling + if instance.Status.Conditions == nil { + reason := dsci.ReconcileInit + message := "Initializing DSCInitialization resource" + SetProgressingCondition(&instance.Status.Conditions, reason, message) + + instance.Status.Phase = PhaseProgressing + err = r.Client.Status().Update(ctx, instance) + if err != nil { + r.Log.Error(err, "Failed to add conditions to status of OCSInitialization resource.", "OCSInitialization", klog.KRef(instance.Namespace, instance.Name)) + return reconcile.Result{}, err + } + } + + // ADD RECONCILIATION LOGIC HERE + + // Finish reconciling + reason := dsci.ReconcileCompleted + message := dsci.ReconcileCompletedMessage + SetCompleteCondition(&instance.Status.Conditions, reason, message) + + instance.Status.Phase = PhaseReady + err = r.Client.Status().Update(ctx, instance) return ctrl.Result{}, nil } // SetupWithManager sets up the controller with the Manager. -func (r *DSCInitiatlizationReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *DSCInitializationReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&dscinitializationv1alpha1.DSCInitiatlization{}). + For(&dsci.DSCInitialization{}). Complete(r) } diff --git a/controllers/status.go b/controllers/status.go new file mode 100644 index 00000000000..7eff68a8969 --- /dev/null +++ b/controllers/status.go @@ -0,0 +1,148 @@ +/* +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 controllers + +import ( + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" + corev1 "k8s.io/api/core/v1" +) + +// These constants represent the overall Phase as used by .Status.Phase +var ( + // PhaseIgnored is used when a resource is ignored + PhaseIgnored = "Ignored" + // PhaseProgressing is used when SetProgressingCondition is called + PhaseProgressing = "Progressing" + // PhaseError is used when SetErrorCondition is called + PhaseError = "Error" + // PhaseReady is used when SetCompleteCondition is called + PhaseReady = "Ready" + // PhaseNotReady is used when waiting for system to be ready + // after reconcile is successful + PhaseNotReady = "Not Ready" + // PhaseClusterExpanding is used when cluster is expanding capacity + PhaseClusterExpanding = "Expanding Capacity" + // PhaseDeleting is used when cluster is deleting + PhaseDeleting = "Deleting" + // PhaseConnecting is used when cluster is connecting to external cluster + PhaseConnecting = "Connecting" + // PhaseOnboarding is used when consumer is Onboarding + PhaseOnboarding = "Onboarding" +) + +const ( + ConditionReconcileComplete conditionsv1.ConditionType = "ReconcileComplete" +) + +const ( + // TODO: update this list of constants with proper reasons for conditions + AReason = "AReason" + AnotherReason = "AnotherReason" +) + +// SetProgressingCondition sets the ProgressingCondition to True and other conditions to +// false or Unknown. Used when we are just starting to reconcile, and there are no existing +// conditions. +func SetProgressingCondition(conditions *[]conditionsv1.Condition, reason string, message string) { + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: ConditionReconcileComplete, + Status: corev1.ConditionUnknown, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionAvailable, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionProgressing, + Status: corev1.ConditionTrue, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionDegraded, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionUpgradeable, + Status: corev1.ConditionUnknown, + Reason: reason, + Message: message, + }) +} + +// SetErrorCondition sets the ConditionReconcileComplete to False in case of any errors +// during the reconciliation process. +func SetErrorCondition(conditions *[]conditionsv1.Condition, reason string, message string) { + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: ConditionReconcileComplete, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + }) +} + +// SetCompleteCondition sets the ConditionReconcileComplete to True and other Conditions +// to indicate that the reconciliation process has completed successfully. +func SetCompleteCondition(conditions *[]conditionsv1.Condition, reason string, message string) { + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: ConditionReconcileComplete, + Status: corev1.ConditionTrue, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionAvailable, + Status: corev1.ConditionTrue, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionProgressing, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionDegraded, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionUpgradeable, + Status: corev1.ConditionTrue, + Reason: reason, + Message: message, + }) +} + +// won't override a status condition of the same type and status +func setStatusConditionIfNotPresent(conditions *[]conditionsv1.Condition, condition conditionsv1.Condition) { + + foundCondition := conditionsv1.FindStatusCondition(*conditions, condition.Type) + if foundCondition != nil && foundCondition.Status == condition.Status { + // already exists + return + } + + conditionsv1.SetStatusCondition(conditions, condition) +} diff --git a/main.go b/main.go index 1445fcd9008..b89beb86276 100644 --- a/main.go +++ b/main.go @@ -89,7 +89,7 @@ func main() { os.Exit(1) } - if err = (&controllers.DSCInitiatlizationReconciler{ + if err = (&controllers.DSCInitializationReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil {