diff --git a/apis/datasciencecluster/v1/datasciencecluster_types.go b/apis/datasciencecluster/v1/datasciencecluster_types.go index 58d680e70c9..6212fde602b 100644 --- a/apis/datasciencecluster/v1/datasciencecluster_types.go +++ b/apis/datasciencecluster/v1/datasciencecluster_types.go @@ -90,10 +90,15 @@ type Components struct { type DataScienceClusterStatus struct { // Phase describes the Phase of DataScienceCluster reconciliation state // This is used by OLM UI to provide status information to the user + // Newer API types should use conditions instead. Phase was essentially a state-machine enumeration field, that contradicted system-design principles and hampered evolution, since adding new enum values breaks backward compatibility. + // Rather than encouraging clients to infer implicit properties from phases, we prefer to explicitly expose the individual conditions that clients need to monitor. + // Known .status.phase are: "Created", "Error", "Ready" "Deleting" Phase string `json:"phase,omitempty"` // Conditions describes the state of the DataScienceCluster resource. // +optional + // standard known .status.conditions.type are: "Available", "Progressing", "Degraded" + // Extra .status.conditions.type are : "ReconcileSuccess" "CapabilityDSPv2Argo" and Ready Conditions []conditionsv1.Condition `json:"conditions,omitempty"` // RelatedObjects is a list of objects created and maintained by this operator. diff --git a/apis/dscinitialization/v1/dscinitialization_types.go b/apis/dscinitialization/v1/dscinitialization_types.go index 7d71c52c995..3a14652f1c0 100644 --- a/apis/dscinitialization/v1/dscinitialization_types.go +++ b/apis/dscinitialization/v1/dscinitialization_types.go @@ -99,11 +99,17 @@ type TrustedCABundleSpec struct { type DSCInitializationStatus struct { // Phase describes the Phase of DSCInitializationStatus // This is used by OLM UI to provide status information to the user + // The pattern of using phase is deprecated. + // Newer API types should use conditions instead. Phase was essentially a state-machine enumeration field, that contradicted system-design principles and hampered evolution, since adding new enum values breaks backward compatibility. + // Rather than encouraging clients to infer implicit properties from phases, we prefer to explicitly expose the individual conditions that clients need to monitor. + // Known .status.phase are: "Created", "Error", "Ready" "Deleting" Phase string `json:"phase,omitempty"` // Conditions describes the state of the DSCInitializationStatus resource // +operator-sdk:csv:customresourcedefinitions:type=status // +optional + // standard known .status.conditions.type are: "Available", "Progressing", "Degraded" + // Extra .status.conditions.type are : "ReconcileSuccess", "CapabilityServiceMesh", "CapabilityServiceMeshAuthorization" Conditions []conditionsv1.Condition `json:"conditions,omitempty"` // RelatedObjects is a list of objects created and maintained by this operator. diff --git a/bundle/manifests/dscinitialization.opendatahub.io_dscinitializations.yaml b/bundle/manifests/dscinitialization.opendatahub.io_dscinitializations.yaml index 67798e35b0e..430cadee49e 100644 --- a/bundle/manifests/dscinitialization.opendatahub.io_dscinitializations.yaml +++ b/bundle/manifests/dscinitialization.opendatahub.io_dscinitializations.yaml @@ -194,8 +194,9 @@ spec: description: DSCInitializationStatus defines the observed state of DSCInitialization. properties: conditions: - description: Conditions describes the state of the DSCInitializationStatus - resource + description: 'Conditions describes the state of the DSCInitializationStatus + resource Known .status.conditions.type are: "Available", "Progressing", + "Degraded" and "ReconcileSuccess"' items: description: |- Condition represents the state of the operator's @@ -228,6 +229,13 @@ spec: description: |- Phase describes the Phase of DSCInitializationStatus This is used by OLM UI to provide status information to the user + The pattern of using phase is deprecated. Newer API types should + use conditions instead. Phase was essentially a state-machine enumeration + field, that contradicted system-design principles and hampered evolution, + since adding new enum values breaks backward compatibility. Rather + than encouraging clients to infer implicit properties from phases, + we prefer to explicitly expose the individual conditions that clients + need to monitor. type: string relatedObjects: description: |- diff --git a/components/codeflare/codeflare.go b/components/codeflare/codeflare.go index 534e58c319e..79ef797a19a 100644 --- a/components/codeflare/codeflare.go +++ b/components/codeflare/codeflare.go @@ -10,11 +10,13 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" ) @@ -63,7 +65,7 @@ func (c *CodeFlare) ReconcileComponent(ctx context.Context, owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, - _ bool) error { + _ bool) (conditionsv1.Condition, error) { l := c.ConfigComponentLogger(logger, ComponentName, dscispec) var imageParamMap = map[string]string{ "codeflare-operator-controller-image": "RELATED_IMAGE_ODH_CODEFLARE_OPERATOR_IMAGE", // no need mcad, embedded in cfo @@ -71,12 +73,11 @@ func (c *CodeFlare) ReconcileComponent(ctx context.Context, enabled := c.GetManagementState() == operatorv1.Managed monitoringEnabled := dscispec.Monitoring.ManagementState == operatorv1.Managed - if enabled { if c.DevFlags != nil { // Download manifests and update paths if err := c.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } // check if the CodeFlare operator is installed: it should not be installed @@ -84,16 +85,16 @@ func (c *CodeFlare) ReconcileComponent(ctx context.Context, dependentOperator := CodeflareOperator if found, err := cluster.OperatorExists(ctx, cli, dependentOperator); err != nil { - return fmt.Errorf("operator exists throws error %w", err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("operator exists throws error %w", err)) } else if found { - return fmt.Errorf("operator %s is found. Please uninstall the operator before enabling %s component", - dependentOperator, ComponentName) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("operator %s is found. Please uninstall the operator before enabling %s component", + dependentOperator, ComponentName)) } // Update image parameters only when we do not have customized manifests set if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (c.DevFlags == nil || len(c.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(ParamsPath, imageParamMap, map[string]string{"namespace": dscispec.ApplicationsNamespace}); err != nil { - return fmt.Errorf("failed update image from %s : %w", CodeflarePath+"/bases", err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed update image from %s : %w", CodeflarePath+"/bases", err)) } } } @@ -103,13 +104,13 @@ func (c *CodeFlare) ReconcileComponent(ctx context.Context, CodeflarePath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests %s: %w", CodeflarePath, err)) } l.Info("apply manifests done") if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 20, 2); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } @@ -117,16 +118,16 @@ func (c *CodeFlare) ReconcileComponent(ctx context.Context, if platform == cluster.ManagedRhods { // inject prometheus codeflare*.rules in to /opt/manifests/monitoring/prometheus/prometheus-configs.yaml if err := c.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } diff --git a/components/component.go b/components/component.go index 213ca9b8152..d55f9977443 100644 --- a/components/component.go +++ b/components/component.go @@ -9,6 +9,7 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" "gopkg.in/yaml.v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -78,8 +79,8 @@ type ManifestsConfig struct { } type ComponentInterface interface { - ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, - owner metav1.Object, DSCISpec *dsciv1.DSCInitializationSpec, platform cluster.Platform, currentComponentStatus bool) error + ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, owner metav1.Object, + DSCISpec *dsciv1.DSCInitializationSpec, platform cluster.Platform, currentComponentStatus bool) (conditionsv1.Condition, error) Cleanup(ctx context.Context, cli client.Client, DSCISpec *dsciv1.DSCInitializationSpec) error GetComponentName() string GetManagementState() operatorv1.ManagementState diff --git a/components/dashboard/dashboard.go b/components/dashboard/dashboard.go index 30937c89673..e0199dc6906 100644 --- a/components/dashboard/dashboard.go +++ b/components/dashboard/dashboard.go @@ -11,6 +11,7 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" corev1 "k8s.io/api/core/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,6 +19,7 @@ import ( dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" ) @@ -67,9 +69,8 @@ func (d *Dashboard) ReconcileComponent(ctx context.Context, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, currentComponentExist bool, -) error { +) (conditionsv1.Condition, error) { var l logr.Logger - if platform == cluster.SelfManagedRhods || platform == cluster.ManagedRhods { l = d.ConfigComponentLogger(logger, ComponentNameDownstream, dscispec) } else { @@ -90,12 +91,12 @@ func (d *Dashboard) ReconcileComponent(ctx context.Context, if enabled { // 1. cleanup OAuth client related secret and CR if dashboard is in 'installed false' status if err := d.cleanOauthClient(ctx, cli, dscispec, currentComponentExist, l); err != nil { - return err + return status.FailedComponentCondition(ComponentNameUpstream, err) } if d.DevFlags != nil { // Download manifests and update paths if err := d.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentNameUpstream, err) } if OverridePath != "" { entryPath = OverridePath @@ -107,23 +108,23 @@ func (d *Dashboard) ReconcileComponent(ctx context.Context, // 2. platform specific RBAC if platform == cluster.OpenDataHub || platform == "" { if err := cluster.UpdatePodSecurityRolebinding(ctx, cli, dscispec.ApplicationsNamespace, "odh-dashboard"); err != nil { - return err + return status.FailedComponentCondition(ComponentNameUpstream, err) } } else { if err := cluster.UpdatePodSecurityRolebinding(ctx, cli, dscispec.ApplicationsNamespace, "rhods-dashboard"); err != nil { - return err + return status.FailedComponentCondition(ComponentNameDownstream, err) } } // 3. Append or Update variable for component to consume extraParamsMap, err := updateKustomizeVariable(ctx, cli, platform, dscispec) if err != nil { - return errors.New("failed to set variable for extraParamsMap") + return status.FailedComponentCondition(ComponentNameUpstream, errors.New("failed to set variable for extraParamsMap")) } // 4. update params.env regardless devFlags is provided of not if err := deploy.ApplyParams(entryPath, imageParamMap, extraParamsMap); err != nil { - return fmt.Errorf("failed to update params.env from %s : %w", entryPath, err) + return status.FailedComponentCondition(ComponentNameUpstream, fmt.Errorf("failed to update params.env from %s : %w", entryPath, err)) } } @@ -133,48 +134,47 @@ func (d *Dashboard) ReconcileComponent(ctx context.Context, case cluster.SelfManagedRhods, cluster.ManagedRhods: // anaconda if err := cluster.CreateSecret(ctx, cli, "anaconda-ce-access", dscispec.ApplicationsNamespace); err != nil { - return fmt.Errorf("failed to create access-secret for anaconda: %w", err) + return status.FailedComponentCondition(ComponentNameDownstream, fmt.Errorf("failed to create access-secret for anaconda: %w", err)) } // Deploy RHOAI manifests if err := deploy.DeployManifestsFromPath(ctx, cli, owner, entryPath, dscispec.ApplicationsNamespace, ComponentNameDownstream, enabled); err != nil { - return fmt.Errorf("failed to apply manifests from %s: %w", PathDownstream, err) + return status.FailedComponentCondition(ComponentNameDownstream, fmt.Errorf("failed to apply manifests from %s: %w", PathDownstream, err)) } l.Info("apply manifests done") if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentNameDownstream, dscispec.ApplicationsNamespace, 20, 3); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentNameDownstream, err) + return status.FailedComponentCondition(ComponentNameDownstream, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentNameDownstream, err)) } } // CloudService Monitoring handling if platform == cluster.ManagedRhods { if err := d.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentNameDownstream); err != nil { - return err + return status.FailedComponentCondition(ComponentNameDownstream, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentNameDownstream, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentNameUpstream), nil default: // Deploy ODH manifests if err := deploy.DeployManifestsFromPath(ctx, cli, owner, entryPath, dscispec.ApplicationsNamespace, ComponentNameUpstream, enabled); err != nil { - return err + return status.FailedComponentCondition(ComponentNameUpstream, fmt.Errorf("failed to apply manifests from %s: %w", entryPath, err)) } l.Info("apply manifests done") if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentNameUpstream, dscispec.ApplicationsNamespace, 20, 3); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentNameUpstream, err) + return status.FailedComponentCondition(ComponentNameUpstream, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentNameUpstream, err)) } } - - return nil + return status.SuccessComponentCondition(ComponentNameUpstream), nil } } diff --git a/components/datasciencepipelines/datasciencepipelines.go b/components/datasciencepipelines/datasciencepipelines.go index 388b54bd707..f5e90086f9f 100644 --- a/components/datasciencepipelines/datasciencepipelines.go +++ b/components/datasciencepipelines/datasciencepipelines.go @@ -11,7 +11,6 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" - corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -70,7 +69,7 @@ func (d *DataSciencePipelines) ReconcileComponent(ctx context.Context, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool, -) error { +) (conditionsv1.Condition, error) { l := d.ConfigComponentLogger(logger, ComponentName, dscispec) var imageParamMap = map[string]string{ // v1 @@ -98,19 +97,19 @@ func (d *DataSciencePipelines) ReconcileComponent(ctx context.Context, if d.DevFlags != nil { // Download manifests and update paths if err := d.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } // skip check if the dependent operator has beeninstalled, this is done in dashboard // Update image parameters only when we do not have customized manifests set if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (d.DevFlags == nil || len(d.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(Path, imageParamMap); err != nil { - return fmt.Errorf("failed to update image from %s : %w", Path, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to update image from %s : %w", Path, err)) } } // Check for existing Argo Workflows if err := UnmanagedArgoWorkFlowExists(ctx, cli); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } @@ -120,32 +119,32 @@ func (d *DataSciencePipelines) ReconcileComponent(ctx context.Context, manifestsPath = filepath.Join(OverlayPath, "odh") } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, manifestsPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests %s: %w", manifestsPath, err)) } l.Info("apply manifests done") // Wait for deployment available if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 20, 2); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } // CloudService Monitoring handling if platform == cluster.ManagedRhods { if err := d.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } func UnmanagedArgoWorkFlowExists(ctx context.Context, @@ -165,8 +164,3 @@ func UnmanagedArgoWorkFlowExists(ctx context.Context, return fmt.Errorf("%s CRD already exists but not deployed by this operator. "+ "Remove existing Argo workflows or set `spec.components.datasciencepipelines.managementState` to Removed to proceed ", ArgoWorkflowCRD) } - -func SetExistingArgoCondition(conditions *[]conditionsv1.Condition, reason, message string) { - status.SetCondition(conditions, string(status.CapabilityDSPv2Argo), reason, message, corev1.ConditionFalse) - status.SetComponentCondition(conditions, ComponentName, status.ReconcileFailed, message, corev1.ConditionFalse) -} diff --git a/components/kserve/kserve.go b/components/kserve/kserve.go index fe2eb2a994d..ac269212793 100644 --- a/components/kserve/kserve.go +++ b/components/kserve/kserve.go @@ -10,12 +10,14 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" infrav1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/infrastructure/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" ) @@ -95,7 +97,7 @@ func (k *Kserve) GetComponentName() string { } func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, - logger logr.Logger, owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) error { + logger logr.Logger, owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) (conditionsv1.Condition, error) { l := k.ConfigComponentLogger(logger, ComponentName, dscispec) // dependentParamMap for odh-model-controller to use. @@ -108,44 +110,46 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, if !enabled { if err := k.removeServerlessFeatures(ctx, dscispec); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } else { // Configure dependencies if err := k.configureServerless(ctx, cli, l, dscispec); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if k.DevFlags != nil { // Download manifests and update paths if err := k.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } } if err := k.configureServiceMesh(ctx, cli, dscispec); err != nil { - return fmt.Errorf("failed configuring service mesh while reconciling kserve component. cause: %w", err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed configuring service mesh while reconciling kserve component. cause: %w", err)) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, Path, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return fmt.Errorf("failed to apply manifests from %s : %w", Path, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests from %s : %w", Path, err)) } l.WithValues("Path", Path).Info("apply manifests done for kserve") if enabled { if err := k.setupKserveConfig(ctx, cli, l, dscispec); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } - - // For odh-model-controller + } + l.WithValues("Path", Path).Info("apply manifests done for kserve") + // For odh-model-controller + if enabled { if err := cluster.UpdatePodSecurityRolebinding(ctx, cli, dscispec.ApplicationsNamespace, "odh-model-controller"); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } // Update image parameters for odh-model-controller if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (k.DevFlags == nil || len(k.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(DependentPath, dependentParamMap); err != nil { - return fmt.Errorf("failed to update image %s: %w", DependentPath, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to update image %s: %w", DependentPath, err)) } } } @@ -153,7 +157,7 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, if err := deploy.DeployManifestsFromPath(ctx, cli, owner, DependentPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { if !strings.Contains(err.Error(), "spec.selector") || !strings.Contains(err.Error(), "field is immutable") { // explicitly ignore error if error contains keywords "spec.selector" and "field is immutable" and return all other error. - return err + return status.FailedComponentCondition(ComponentName, err) } } l.WithValues("Path", Path).Info("apply manifests done for odh-model-controller") @@ -161,7 +165,7 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, // Wait for deployment available if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 20, 3); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } @@ -169,12 +173,12 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, if platform == cluster.ManagedRhods { // kesrve rules if err := k.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } func (k *Kserve) Cleanup(ctx context.Context, cli client.Client, instance *dsciv1.DSCInitializationSpec) error { diff --git a/components/kueue/kueue.go b/components/kueue/kueue.go index 81bdbfe5638..1233cdf8de3 100644 --- a/components/kueue/kueue.go +++ b/components/kueue/kueue.go @@ -8,11 +8,13 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" ) @@ -54,7 +56,7 @@ func (k *Kueue) GetComponentName() string { } func (k *Kueue) ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, - owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) error { + owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) (conditionsv1.Condition, error) { l := k.ConfigComponentLogger(logger, ComponentName, dscispec) var imageParamMap = map[string]string{ "odh-kueue-controller-image": "RELATED_IMAGE_ODH_KUEUE_CONTROLLER_IMAGE", // new kueue image @@ -62,44 +64,45 @@ func (k *Kueue) ReconcileComponent(ctx context.Context, cli client.Client, logge enabled := k.GetManagementState() == operatorv1.Managed monitoringEnabled := dscispec.Monitoring.ManagementState == operatorv1.Managed + if enabled { if k.DevFlags != nil { // Download manifests and update paths if err := k.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (k.DevFlags == nil || len(k.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(Path, imageParamMap); err != nil { - return fmt.Errorf("failed to update image from %s : %w", Path, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to update image from %s : %w", Path, err)) } } } // Deploy Kueue Operator if err := deploy.DeployManifestsFromPath(ctx, cli, owner, Path, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return fmt.Errorf("failed to apply manifetss %s: %w", Path, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests %s: %w", Path, err)) } l.Info("apply manifests done") if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 20, 2); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } // CloudService Monitoring handling if platform == cluster.ManagedRhods { if err := k.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } diff --git a/components/modelmeshserving/modelmeshserving.go b/components/modelmeshserving/modelmeshserving.go index b6b067221b9..08e2e97af53 100644 --- a/components/modelmeshserving/modelmeshserving.go +++ b/components/modelmeshserving/modelmeshserving.go @@ -10,11 +10,13 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" ) @@ -78,7 +80,7 @@ func (m *ModelMeshServing) ReconcileComponent(ctx context.Context, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool, -) error { +) (conditionsv1.Condition, error) { l := m.ConfigComponentLogger(logger, ComponentName, dscispec) var imageParamMap = map[string]string{ "odh-mm-rest-proxy": "RELATED_IMAGE_ODH_MM_REST_PROXY_IMAGE", @@ -101,7 +103,7 @@ func (m *ModelMeshServing) ReconcileComponent(ctx context.Context, if m.DevFlags != nil { // Download manifests and update paths if err := m.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } @@ -110,37 +112,37 @@ func (m *ModelMeshServing) ReconcileComponent(ctx context.Context, "modelmesh-controller", "odh-prometheus-operator", "prometheus-custom"); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } // Update image parameters if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (m.DevFlags == nil || len(m.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(Path, imageParamMap); err != nil { - return fmt.Errorf("failed update image from %s : %w", Path, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed update image from %s : %w", Path, err)) } } } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, Path, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return fmt.Errorf("failed to apply manifests from %s : %w", Path, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests from %s : %w", Path, err)) } l.WithValues("Path", Path).Info("apply manifests done for modelmesh") // For odh-model-controller if enabled { if err := cluster.UpdatePodSecurityRolebinding(ctx, cli, dscispec.ApplicationsNamespace, "odh-model-controller"); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } // Update image parameters for odh-model-controller if dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "" { if err := deploy.ApplyParams(DependentPath, dependentImageParamMap); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, DependentPath, dscispec.ApplicationsNamespace, m.GetComponentName(), enabled); err != nil { // explicitly ignore error if error contains keywords "spec.selector" and "field is immutable" and return all other error. if !strings.Contains(err.Error(), "spec.selector") || !strings.Contains(err.Error(), "field is immutable") { - return err + return status.FailedComponentCondition(ComponentName, err) } } @@ -148,7 +150,7 @@ func (m *ModelMeshServing) ReconcileComponent(ctx context.Context, if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 20, 2); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } @@ -156,20 +158,20 @@ func (m *ModelMeshServing) ReconcileComponent(ctx context.Context, if platform == cluster.ManagedRhods { // first model-mesh rules if err := m.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } // then odh-model-controller rules if err := m.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, DependentComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests: %w", err)) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } diff --git a/components/modelregistry/modelregistry.go b/components/modelregistry/modelregistry.go index 5d0610019e5..f6660562003 100644 --- a/components/modelregistry/modelregistry.go +++ b/components/modelregistry/modelregistry.go @@ -12,6 +12,7 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -19,6 +20,7 @@ import ( dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" infrav1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/infrastructure/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/conversion" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" @@ -70,7 +72,7 @@ func (m *ModelRegistry) GetComponentName() string { } func (m *ModelRegistry) ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, - owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) error { + owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) (conditionsv1.Condition, error) { l := m.ConfigComponentLogger(logger, ComponentName, dscispec) var imageParamMap = map[string]string{ "IMAGES_MODELREGISTRY_OPERATOR": "RELATED_IMAGE_ODH_MODEL_REGISTRY_OPERATOR_IMAGE", @@ -83,17 +85,17 @@ func (m *ModelRegistry) ReconcileComponent(ctx context.Context, cli client.Clien if enabled { // return error if ServiceMesh is not enabled, as it's a required feature if dscispec.ServiceMesh == nil || dscispec.ServiceMesh.ManagementState != operatorv1.Managed { - return errors.New("ServiceMesh needs to be set to 'Managed' in DSCI CR, it is required by Model Registry") + return status.FailedComponentCondition(ComponentName, errors.New("ServiceMesh needs to be set to 'Managed' in DSCI CR, required by Model Registry")) } if err := m.createDependencies(ctx, cli, dscispec); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if m.DevFlags != nil { // Download manifests and update paths if err := m.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } @@ -103,7 +105,7 @@ func (m *ModelRegistry) ReconcileComponent(ctx context.Context, cli client.Clien "DEFAULT_CERT": DefaultModelRegistryCert, } if err := deploy.ApplyParams(Path, imageParamMap, extraParamsMap); err != nil { - return fmt.Errorf("failed to update image from %s : %w", Path, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to update image from %s : %w", Path, err)) } } @@ -111,54 +113,53 @@ func (m *ModelRegistry) ReconcileComponent(ctx context.Context, cli client.Clien // We do not delete this namespace even when ModelRegistry is Removed or when operator is uninstalled. ns, err := cluster.CreateNamespace(ctx, cli, ModelRegistriesNamespace) if err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("created model registry namespace", "namespace", ModelRegistriesNamespace) // create servicemeshmember here, for now until post MVP solution - err = enrollToServiceMesh(ctx, cli, dscispec, ns) - if err != nil { - return err + if err = enrollToServiceMesh(ctx, cli, dscispec, ns); err != nil { + return status.FailedComponentCondition(ComponentName, err) } l.Info("created model registry servicemesh member", "namespace", ModelRegistriesNamespace) } else { - err := m.removeDependencies(ctx, cli, dscispec) - if err != nil { - return err + if err := m.removeDependencies(ctx, cli, dscispec); err != nil { + return status.FailedComponentCondition(ComponentName, err) } } // Deploy ModelRegistry Operator if err := deploy.DeployManifestsFromPath(ctx, cli, owner, Path, dscispec.ApplicationsNamespace, m.GetComponentName(), enabled); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests from %s : %w", Path, err)) } l.Info("apply manifests done") // Create additional model registry resources, componentEnabled=true because these extras are never deleted! if err := deploy.DeployManifestsFromPath(ctx, cli, owner, Path+"/extras", dscispec.ApplicationsNamespace, m.GetComponentName(), true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests from %s : %w", Path, err)) } l.Info("apply extra manifests done") if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, m.GetComponentName(), dscispec.ApplicationsNamespace, 10, 1); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } // CloudService Monitoring handling if platform == cluster.ManagedRhods { if err := m.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests from %s : %w", Path, err)) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests from %s : %w", Path, err)) } l.Info("updating SRE monitoring done") } - return nil + + return status.SuccessComponentCondition(ComponentName), nil } func (m *ModelRegistry) createDependencies(ctx context.Context, cli client.Client, dscispec *dsciv1.DSCInitializationSpec) error { diff --git a/components/ray/ray.go b/components/ray/ray.go index c1720cfcd6c..8e17bf7fe24 100644 --- a/components/ray/ray.go +++ b/components/ray/ray.go @@ -10,11 +10,13 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" ) @@ -56,7 +58,7 @@ func (r *Ray) GetComponentName() string { } func (r *Ray) ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, - owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) error { + owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) (conditionsv1.Condition, error) { l := r.ConfigComponentLogger(logger, ComponentName, dscispec) var imageParamMap = map[string]string{ @@ -65,45 +67,44 @@ func (r *Ray) ReconcileComponent(ctx context.Context, cli client.Client, logger enabled := r.GetManagementState() == operatorv1.Managed monitoringEnabled := dscispec.Monitoring.ManagementState == operatorv1.Managed - if enabled { if r.DevFlags != nil { // Download manifests and update paths if err := r.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (r.DevFlags == nil || len(r.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(RayPath, imageParamMap, map[string]string{"namespace": dscispec.ApplicationsNamespace}); err != nil { - return fmt.Errorf("failed to update image from %s : %w", RayPath, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to update image from %s : %w", RayPath, err)) } } } // Deploy Ray Operator if err := deploy.DeployManifestsFromPath(ctx, cli, owner, RayPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return fmt.Errorf("failed to apply manifets from %s : %w", RayPath, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests from %s : %w", RayPath, err)) } l.Info("apply manifests done") if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 20, 2); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } // CloudService Monitoring handling if platform == cluster.ManagedRhods { if err := r.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } diff --git a/components/trainingoperator/trainingoperator.go b/components/trainingoperator/trainingoperator.go index 6ebe12f538b..68fae7c023d 100644 --- a/components/trainingoperator/trainingoperator.go +++ b/components/trainingoperator/trainingoperator.go @@ -10,11 +10,13 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" ) @@ -33,10 +35,10 @@ type TrainingOperator struct { components.Component `json:""` } -func (r *TrainingOperator) OverrideManifests(ctx context.Context, _ cluster.Platform) error { +func (t *TrainingOperator) OverrideManifests(ctx context.Context, _ cluster.Platform) error { // If devflags are set, update default manifests path - if len(r.DevFlags.Manifests) != 0 { - manifestConfig := r.DevFlags.Manifests[0] + if len(t.DevFlags.Manifests) != 0 { + manifestConfig := t.DevFlags.Manifests[0] if err := deploy.DownloadManifests(ctx, ComponentName, manifestConfig); err != nil { return err } @@ -51,59 +53,59 @@ func (r *TrainingOperator) OverrideManifests(ctx context.Context, _ cluster.Plat return nil } -func (r *TrainingOperator) GetComponentName() string { +func (t *TrainingOperator) GetComponentName() string { return ComponentName } -func (r *TrainingOperator) ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, - owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) error { - l := r.ConfigComponentLogger(logger, ComponentName, dscispec) +func (t *TrainingOperator) ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, + owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) (conditionsv1.Condition, error) { + l := t.ConfigComponentLogger(logger, ComponentName, dscispec) var imageParamMap = map[string]string{ "odh-training-operator-controller-image": "RELATED_IMAGE_ODH_TRAINING_OPERATOR_IMAGE", } - enabled := r.GetManagementState() == operatorv1.Managed + enabled := t.GetManagementState() == operatorv1.Managed monitoringEnabled := dscispec.Monitoring.ManagementState == operatorv1.Managed if enabled { - if r.DevFlags != nil { + if t.DevFlags != nil { // Download manifests and update paths - if err := r.OverrideManifests(ctx, platform); err != nil { - return err + if err := t.OverrideManifests(ctx, platform); err != nil { + return status.FailedComponentCondition(ComponentName, err) } } - if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (r.DevFlags == nil || len(r.DevFlags.Manifests) == 0) { + if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (t.DevFlags == nil || len(t.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(TrainingOperatorPath, imageParamMap); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } } // Deploy Training Operator if err := deploy.DeployManifestsFromPath(ctx, cli, owner, TrainingOperatorPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests %s: %w", TrainingOperatorPath, err)) } l.Info("apply manifests done") if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 20, 2); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } // CloudService Monitoring handling if platform == cluster.ManagedRhods { - if err := r.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + if err := t.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { + return status.FailedComponentCondition(ComponentName, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } diff --git a/components/trustyai/trustyai.go b/components/trustyai/trustyai.go index d7d67d237bd..95b378b7959 100644 --- a/components/trustyai/trustyai.go +++ b/components/trustyai/trustyai.go @@ -9,11 +9,13 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" ) @@ -55,7 +57,7 @@ func (t *TrustyAI) GetComponentName() string { } func (t *TrustyAI) ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, - owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) error { + owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) (conditionsv1.Condition, error) { var imageParamMap = map[string]string{ "trustyaiServiceImage": "RELATED_IMAGE_ODH_TRUSTYAI_SERVICE_IMAGE", "trustyaiOperatorImage": "RELATED_IMAGE_ODH_TRUSTYAI_SERVICE_OPERATOR_IMAGE", @@ -69,40 +71,40 @@ func (t *TrustyAI) ReconcileComponent(ctx context.Context, cli client.Client, lo if t.DevFlags != nil { // Download manifests and update paths if err := t.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (t.DevFlags == nil || len(t.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(Path, imageParamMap); err != nil { - return fmt.Errorf("failed to update image %s: %w", Path, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to update image %s: %w", Path, err)) } } } // Deploy TrustyAI Operator if err := deploy.DeployManifestsFromPath(ctx, cli, owner, Path, dscispec.ApplicationsNamespace, t.GetComponentName(), enabled); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifetss %s: %w", Path, err)) } l.Info("apply manifests done") // Wait for deployment available if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 10, 2); err != nil { - return fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployment for %s is not ready to server: %w", ComponentName, err)) } } // CloudService Monitoring handling if platform == cluster.ManagedRhods { if err := t.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } diff --git a/components/workbenches/workbenches.go b/components/workbenches/workbenches.go index a10addccf70..c182a4a3229 100644 --- a/components/workbenches/workbenches.go +++ b/components/workbenches/workbenches.go @@ -10,11 +10,13 @@ import ( "github.com/go-logr/logr" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/components" + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/status" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/metadata/labels" @@ -97,7 +99,7 @@ func (w *Workbenches) GetComponentName() string { } func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, logger logr.Logger, - owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) error { + owner metav1.Object, dscispec *dsciv1.DSCInitializationSpec, platform cluster.Platform, _ bool) (conditionsv1.Condition, error) { l := w.ConfigComponentLogger(logger, ComponentName, dscispec) var imageParamMap = map[string]string{ "odh-notebook-controller-image": "RELATED_IMAGE_ODH_NOTEBOOK_CONTROLLER_IMAGE", @@ -114,7 +116,7 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, if w.DevFlags != nil { // Download manifests and update paths if err := w.OverrideManifests(ctx, platform); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } if platform == cluster.SelfManagedRhods || platform == cluster.ManagedRhods { @@ -122,17 +124,17 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, // Specifying this label triggers its deletion when the operator is uninstalled. _, err := cluster.CreateNamespace(ctx, cli, "rhods-notebooks", cluster.WithLabels(labels.ODH.OwnedNamespace, "true")) if err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } // Update Default rolebinding err := cluster.UpdatePodSecurityRolebinding(ctx, cli, dscispec.ApplicationsNamespace, "notebook-controller-service-account") if err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, notebookControllerPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return fmt.Errorf("failed to apply manifetss %s: %w", notebookControllerPath, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifests %s: %w", notebookControllerPath, err)) } l.WithValues("Path", notebookControllerPath).Info("apply manifests done NBC") @@ -141,11 +143,11 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (w.DevFlags == nil || len(w.DevFlags.Manifests) == 0) { // for kf-notebook-controller image if err := deploy.ApplyParams(notebookControllerPath, imageParamMap); err != nil { - return fmt.Errorf("failed to update image %s: %w", notebookControllerPath, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to update image %s: %w", notebookControllerPath, err)) } // for odh-notebook-controller image if err := deploy.ApplyParams(kfnotebookControllerPath, imageParamMap); err != nil { - return fmt.Errorf("failed to update image %s: %w", kfnotebookControllerPath, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to update image %s: %w", kfnotebookControllerPath, err)) } } } @@ -157,7 +159,7 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, kfnotebookControllerPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifetss %s: %w", kfnotebookControllerPath, err)) } manifestsPath = notebookImagesPath } else { @@ -167,30 +169,30 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, manifestsPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { - return err + return status.FailedComponentCondition(ComponentName, fmt.Errorf("failed to apply manifetss %s: %w", manifestsPath, err)) } l.WithValues("Path", manifestsPath).Info("apply manifests done notebook image") // Wait for deployment available if enabled { if err := cluster.WaitForDeploymentAvailable(ctx, cli, ComponentName, dscispec.ApplicationsNamespace, 10, 2); err != nil { - return fmt.Errorf("deployments for %s are not ready to server: %w", ComponentName, err) + return status.FailedComponentCondition(ComponentName, fmt.Errorf("deployments for %s are not ready to server: %w", ComponentName, err)) } } // CloudService Monitoring handling if platform == cluster.ManagedRhods { if err := w.UpdatePrometheusConfig(cli, l, enabled && monitoringEnabled, ComponentName); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } if err := deploy.DeployManifestsFromPath(ctx, cli, owner, filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), dscispec.Monitoring.Namespace, "prometheus", true); err != nil { - return err + return status.FailedComponentCondition(ComponentName, err) } l.Info("updating SRE monitoring done") } - return nil + return status.SuccessComponentCondition(ComponentName), nil } diff --git a/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml b/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml index 327c95af25a..97686c3c3a9 100644 --- a/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml +++ b/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml @@ -634,8 +634,10 @@ spec: description: DataScienceClusterStatus defines the observed state of DataScienceCluster. properties: conditions: - description: Conditions describes the state of the DataScienceCluster - resource. + description: |- + Conditions describes the state of the DataScienceCluster resource. + standard known .status.conditions.type are: "Available", "Progressing", "Degraded" + Extra .status.conditions.type are : "ReconcileSuccess" "CapabilityDSPv2Argo" and Ready items: description: |- Condition represents the state of the operator's @@ -673,6 +675,9 @@ spec: description: |- Phase describes the Phase of DataScienceCluster reconciliation state This is used by OLM UI to provide status information to the user + Newer API types should use conditions instead. Phase was essentially a state-machine enumeration field, that contradicted system-design principles and hampered evolution, since adding new enum values breaks backward compatibility. + Rather than encouraging clients to infer implicit properties from phases, we prefer to explicitly expose the individual conditions that clients need to monitor. + Known .status.phase are: "Created", "Error", "Ready" "Deleting" type: string relatedObjects: description: |- diff --git a/config/crd/bases/dscinitialization.opendatahub.io_dscinitializations.yaml b/config/crd/bases/dscinitialization.opendatahub.io_dscinitializations.yaml index 0833b65acb0..e2c0a1716e7 100644 --- a/config/crd/bases/dscinitialization.opendatahub.io_dscinitializations.yaml +++ b/config/crd/bases/dscinitialization.opendatahub.io_dscinitializations.yaml @@ -194,8 +194,10 @@ spec: description: DSCInitializationStatus defines the observed state of DSCInitialization. properties: conditions: - description: Conditions describes the state of the DSCInitializationStatus - resource + description: |- + Conditions describes the state of the DSCInitializationStatus resource + standard known .status.conditions.type are: "Available", "Progressing", "Degraded" + Extra .status.conditions.type are : "ReconcileSuccess", "CapabilityServiceMesh", "CapabilityServiceMeshAuthorization" items: description: |- Condition represents the state of the operator's @@ -228,6 +230,10 @@ spec: description: |- Phase describes the Phase of DSCInitializationStatus This is used by OLM UI to provide status information to the user + The pattern of using phase is deprecated. + Newer API types should use conditions instead. Phase was essentially a state-machine enumeration field, that contradicted system-design principles and hampered evolution, since adding new enum values breaks backward compatibility. + Rather than encouraging clients to infer implicit properties from phases, we prefer to explicitly expose the individual conditions that clients need to monitor. + Known .status.phase are: "Created", "Error", "Ready" "Deleting" type: string relatedObjects: description: |- diff --git a/config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml b/config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml index da6372f5889..6352a5944d4 100644 --- a/config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml @@ -55,8 +55,9 @@ spec: displayName: Dev Flags path: devFlags statusDescriptors: - - description: Conditions describes the state of the DSCInitializationStatus - resource + - description: 'Conditions describes the state of the DSCInitializationStatus + resource Known .status.conditions.type are: "Available", "Progressing", + "Degraded" and "ReconcileSuccess"' displayName: Conditions path: conditions version: v1 diff --git a/controllers/datasciencecluster/datasciencecluster_controller.go b/controllers/datasciencecluster/datasciencecluster_controller.go index 088f3e308d9..6fd1b6cb389 100644 --- a/controllers/datasciencecluster/datasciencecluster_controller.go +++ b/controllers/datasciencecluster/datasciencecluster_controller.go @@ -29,6 +29,7 @@ import ( buildv1 "github.com/openshift/api/build/v1" imagev1 "github.com/openshift/api/image/v1" operatorv1 "github.com/openshift/api/operator/v1" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -82,12 +83,12 @@ const ( // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. func (r *DataScienceClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:maintidx,gocyclo - r.Log.Info("Reconciling DataScienceCluster resources", "Request.Name", req.Name) + r.Log.Info("Reconciling DataScienceCluster.", "DataScienceCluster Request.Name", req.Name) // Get information on version currentOperatorReleaseVersion, err := cluster.GetRelease(ctx, r.Client) if err != nil { - r.Log.Error(err, "failed to get operator release version") + r.Log.Error(err, "Failed to get operator release version") return ctrl.Result{}, err } @@ -122,6 +123,15 @@ func (r *DataScienceClusterReconciler) Reconcile(ctx context.Context, req ctrl.R // delete DSC CR and let reconcile requeue // sometimes with finalizer DSC CR won't get deleted, force to remove finalizer here if upgrade.HasDeleteConfigMap(ctx, r.Client) { + instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(dsc *dscv1.DataScienceCluster) { + // set Available to false, ready to delete + status.UpdateCondition(&dsc.Status.Conditions, *status.UnavailableCondition(status.TerminatingReason, status.TerminatingMessage)) + dsc.Status.Phase = status.PhaseDeleting + }) + if err != nil { + r.Log.Error(err, "Failed to update DataScienceCluster conditions when deleting DSC CR") + // continue with deletion + } if controllerutil.ContainsFinalizer(instance, finalizerName) { if controllerutil.RemoveFinalizer(instance, finalizerName) { if err := r.Update(ctx, instance); err != nil { @@ -157,17 +167,15 @@ func (r *DataScienceClusterReconciler) Reconcile(ctx context.Context, req ctrl.R // Update phase to error state if DataScienceCluster is created without valid DSCInitialization switch len(dsciInstances.Items) { // only handle number as 0 or 1, others won't be existed since webhook block creation case 0: - reason := status.ReconcileFailed - message := "Failed to get a valid DSCInitialization instance, please create a DSCI instance" - r.Log.Info(message) - instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dscv1.DataScienceCluster) { - status.SetProgressingCondition(&saved.Status.Conditions, reason, message) - // Patch Degraded with True status - status.SetCondition(&saved.Status.Conditions, "Degraded", reason, message, corev1.ConditionTrue) - saved.Status.Phase = status.PhaseError + + r.Log.Info(status.DSCIMissingMessage) + instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(dsc *dscv1.DataScienceCluster) { + // set Available to false, waiting for DSCI + status.UpdateCondition(&dsc.Status.Conditions, *status.UnavailableCondition(status.DSCIMissingReason, status.DSCIMissingMessage)) + dsc.Status.Phase = status.PhaseError }) if err != nil { - r.reportError(err, instance, "failed to update DataScienceCluster condition") + r.reportError(err, instance, "Failed to update conditions to DataScienceCluster") return ctrl.Result{}, err } @@ -207,14 +215,12 @@ func (r *DataScienceClusterReconciler) Reconcile(ctx context.Context, req ctrl.R } // Check preconditions if this is an upgrade if instance.Status.Phase == status.PhaseReady { - // Check for existence of Argo Workflows if DSP is + // Check for existence of Argo Workflows if instance.Status.InstalledComponents[datasciencepipelines.ComponentName] { if err := datasciencepipelines.UnmanagedArgoWorkFlowExists(ctx, r.Client); err != nil { - message := fmt.Sprintf("Failed upgrade: %v ", err.Error()) - _, err = status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dscv1.DataScienceCluster) { - datasciencepipelines.SetExistingArgoCondition(&saved.Status.Conditions, status.ArgoWorkflowExist, message) - status.SetErrorCondition(&saved.Status.Conditions, status.ArgoWorkflowExist, message) - saved.Status.Phase = status.PhaseError + _, err = status.UpdateWithRetry(ctx, r.Client, instance, func(dsc *dscv1.DataScienceCluster) { + status.UpdateCondition(&dsc.Status.Conditions, status.ArgoExistCondition(status.ArgoWorkflowExistReason, "Failed upgrade: "+err.Error())) + dsc.Status.Phase = status.PhaseError }) return ctrl.Result{}, err } @@ -223,14 +229,13 @@ func (r *DataScienceClusterReconciler) Reconcile(ctx context.Context, req ctrl.R // Start reconciling if instance.Status.Conditions == nil { - reason := status.ReconcileInit - message := "Initializing DataScienceCluster resource" - instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dscv1.DataScienceCluster) { - status.SetProgressingCondition(&saved.Status.Conditions, reason, message) - saved.Status.Phase = status.PhaseProgressing + instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(dsc *dscv1.DataScienceCluster) { + status.UpdateCondition(&dsc.Status.Conditions, *status.InitControllerCondition(status.DSCReconcileStartMessage)) + + dsc.Status.Phase = status.PhaseCreated }) if err != nil { - _ = r.reportError(err, instance, fmt.Sprintf("failed to add conditions to status of DataScienceCluster resource name %s", req.Name)) + _ = r.reportError(err, instance, "Failed to add conditions to status of DataScienceCluster resource name "+req.Name) return ctrl.Result{}, err } @@ -248,13 +253,14 @@ func (r *DataScienceClusterReconciler) Reconcile(ctx context.Context, req ctrl.R // Process errors for components if componentErrors != nil { r.Log.Info("DataScienceCluster Deployment Incomplete.") - instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dscv1.DataScienceCluster) { - status.SetCompleteCondition(&saved.Status.Conditions, status.ReconcileCompletedWithComponentErrors, + instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(dsc *dscv1.DataScienceCluster) { + status.SetErrorCondition(&dsc.Status.Conditions, status.ReconcileCompletedWithComponentErrorsReason, fmt.Sprintf("DataScienceCluster resource reconciled with component errors: %v", componentErrors)) - saved.Status.Phase = status.PhaseReady + // Ref: https://github.com/opendatahub-io/opendatahub-operator/pull/779 + dsc.Status.Phase = status.PhaseError }) if err != nil { - r.Log.Error(err, "failed to update DataScienceCluster conditions with incompleted reconciliation") + r.Log.Error(err, "Failed to update DataScienceCluster conditions with incompleted reconciliation") return ctrl.Result{}, err } @@ -265,21 +271,23 @@ func (r *DataScienceClusterReconciler) Reconcile(ctx context.Context, req ctrl.R } // finalize reconciliation - instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dscv1.DataScienceCluster) { - status.SetCompleteCondition(&saved.Status.Conditions, status.ReconcileCompleted, "DataScienceCluster resource reconciled successfully") - saved.Status.Phase = status.PhaseReady - saved.Status.Release = currentOperatorReleaseVersion + instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(dsc *dscv1.DataScienceCluster) { + status.SetCompleteCondition(&dsc.Status.Conditions, status.ReconcileSuccessReason, status.DSCReconcileSuccessMessage) + // remove previous condition argo workflow + status.RemoveComponentCondition(&dsc.Status.Conditions, status.CapabilityDSPv2Argo) + dsc.Status.Phase = status.PhaseReady + dsc.Status.Release = currentOperatorReleaseVersion }) if err != nil { - r.Log.Error(err, "failed to update DataScienceCluster conditions after successfully completed reconciliation") + r.Log.Error(err, "Failed to update DataScienceCluster conditions after successfully completed reconciliation") return ctrl.Result{}, err } - r.Log.Info("DataScienceCluster Deployment Completed.") + r.Log.Info("DataScienceCluster Completed.") r.Recorder.Eventf(instance, corev1.EventTypeNormal, "DataScienceClusterCreationSuccessful", - "DataScienceCluster instance %s created and deployed successfully", instance.Name) + "DataScienceCluster instance %s completed successfully", instance.Name) return ctrl.Result{}, nil } @@ -295,15 +303,11 @@ func (r *DataScienceClusterReconciler) reconcileSubComponent(ctx context.Context // First set conditions to reflect a component is about to be reconciled // only set to init condition e.g Unknonw for the very first time when component is not in the list if !isExistStatus { - message := "Component is disabled" - if enabled { - message = "Component is enabled" - } instance, err := status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dscv1.DataScienceCluster) { - status.SetComponentCondition(&saved.Status.Conditions, componentName, status.ReconcileInit, message, corev1.ConditionUnknown) + status.NewComponentCondition(&saved.Status.Conditions, componentName, enabled) }) if err != nil { - _ = r.reportError(err, instance, "failed to update DataScienceCluster conditions before first time reconciling "+componentName) + _ = r.reportError(err, instance, "Failed to update DataScienceCluster conditions before first time reconciling "+componentName) // try to continue with reconciliation, as further updates can fix the status } } @@ -314,39 +318,43 @@ func (r *DataScienceClusterReconciler) reconcileSubComponent(ctx context.Context r.Log.Error(err, "Failed to determine platform") return instance, err } - err = component.ReconcileComponent(ctx, r.Client, r.Log, instance, r.DataScienceCluster.DSCISpec, platform, installedComponentValue) - + reconcileCondition, err := component.ReconcileComponent(ctx, r.Client, r.Log, instance, r.DataScienceCluster.DSCISpec, platform, installedComponentValue) if err != nil { // reconciliation failed: log errors, raise event and update status accordingly - instance = r.reportError(err, instance, "failed to reconcile "+componentName+" on DataScienceCluster") - instance, _ = status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dscv1.DataScienceCluster) { + instance = r.reportError(err, instance, "Failed to reconcile "+componentName+" on DataScienceCluster") + instance, _ = status.UpdateWithRetry(ctx, r.Client, instance, func(dsc *dscv1.DataScienceCluster) { if enabled { + // special handle on DSP if strings.Contains(err.Error(), datasciencepipelines.ArgoWorkflowCRD+" CRD already exists") { - datasciencepipelines.SetExistingArgoCondition(&saved.Status.Conditions, status.ArgoWorkflowExist, fmt.Sprintf("Component update failed: %v", err)) + status.UpdateCondition(&dsc.Status.Conditions, + status.ArgoExistCondition(status.ArgoWorkflowExistReason, "datasciencepipeline update failed: "+err.Error()), + ) } else { - status.SetComponentCondition(&saved.Status.Conditions, componentName, status.ReconcileFailed, fmt.Sprintf("Component reconciliation failed: %v", err), corev1.ConditionFalse) + status.UpdateCondition(&dsc.Status.Conditions, reconcileCondition) } } else { - status.SetComponentCondition(&saved.Status.Conditions, componentName, status.ReconcileFailed, fmt.Sprintf("Component removal failed: %v", err), corev1.ConditionFalse) + reconcileCondition.Reason = status.RemoveFailedReason + status.UpdateCondition(&dsc.Status.Conditions, reconcileCondition) } }) return instance, err } // reconciliation succeeded: update status accordingly - instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dscv1.DataScienceCluster) { - if saved.Status.InstalledComponents == nil { - saved.Status.InstalledComponents = make(map[string]bool) + instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(dsc *dscv1.DataScienceCluster) { + // TODO: get rid of InstalledComponents map from DSC .status, should use .status..conditions..stutus to check + // this need to work with dashboard together and or maybe QE to be notified too + if dsc.Status.InstalledComponents == nil { + dsc.Status.InstalledComponents = make(map[string]bool) } - saved.Status.InstalledComponents[componentName] = enabled + dsc.Status.InstalledComponents[componentName] = enabled if enabled { - status.SetComponentCondition(&saved.Status.Conditions, componentName, status.ReconcileCompleted, "Component reconciled successfully", corev1.ConditionTrue) + status.UpdateCondition(&dsc.Status.Conditions, reconcileCondition) } else { - status.RemoveComponentCondition(&saved.Status.Conditions, componentName) + status.RemoveComponentCondition(&dsc.Status.Conditions, conditionsv1.ConditionType(componentName+status.PhaseReady)) } }) if err != nil { - instance = r.reportError(err, instance, "failed to update DataScienceCluster status after reconciling "+componentName) - + instance = r.reportError(err, instance, "Failed to update DataScienceCluster status after reconciling "+componentName) return instance, err } diff --git a/controllers/dscinitialization/dscinitialization_controller.go b/controllers/dscinitialization/dscinitialization_controller.go index 7003547c1a6..d9adb5d31de 100644 --- a/controllers/dscinitialization/dscinitialization_controller.go +++ b/controllers/dscinitialization/dscinitialization_controller.go @@ -80,7 +80,7 @@ func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Re currentOperatorReleaseVersion, err := cluster.GetRelease(ctx, r.Client) if err != nil { - r.Log.Error(err, "failed to get operator release version") + r.Log.Error(err, "Failed to get operator release version") return ctrl.Result{}, err } @@ -137,16 +137,14 @@ func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Re // Start reconciling if instance.Status.Conditions == nil { - reason := status.ReconcileInit - message := "Initializing DSCInitialization resource" - instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(saved *dsciv1.DSCInitialization) { - status.SetProgressingCondition(&saved.Status.Conditions, reason, message) - saved.Status.Phase = status.PhaseProgressing + instance, err = status.UpdateWithRetry(ctx, r.Client, instance, func(dsci *dsciv1.DSCInitialization) { + status.UpdateCondition(&dsci.Status.Conditions, *status.InitControllerCondition(status.DSCIReconcileStartMessage)) + dsci.Status.Phase = status.PhaseCreated }) if err != nil { r.Log.Error(err, "Failed to add conditions to status of DSCInitialization resource.", "DSCInitialization", req.Namespace, "Request.Name", req.Name) r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", - "%s for instance %s", message, instance.Name) + "%s for instance %s", status.DSCIReconcileFailedMessage, instance.Name) return reconcile.Result{}, err } @@ -251,20 +249,23 @@ func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Re // Apply Service Mesh configurations if errServiceMesh := r.configureServiceMesh(ctx, instance); errServiceMesh != nil { + _, _ = status.UpdateWithRetry(ctx, r.Client, instance, func(dsci *dsciv1.DSCInitialization) { + status.SetErrorCondition(&dsci.Status.Conditions, status.CapabilityFailed, status.DSCIReconcileFailedMessage) + dsci.Status.Phase = status.PhaseError + }) return reconcile.Result{}, errServiceMesh } // Finish reconciling - _, err = status.UpdateWithRetry[*dsciv1.DSCInitialization](ctx, r.Client, instance, func(saved *dsciv1.DSCInitialization) { - status.SetCompleteCondition(&saved.Status.Conditions, status.ReconcileCompleted, status.ReconcileCompletedMessage) - saved.Status.Phase = status.PhaseReady - saved.Status.Release = currentOperatorReleaseVersion + _, err = status.UpdateWithRetry[*dsciv1.DSCInitialization](ctx, r.Client, instance, func(dsci *dsciv1.DSCInitialization) { + status.SetCompleteCondition(&dsci.Status.Conditions, status.ReconcileSuccessReason, status.DSCIReconcileSuccessMessage) + dsci.Status.Phase = status.PhaseReady + dsci.Status.Release = currentOperatorReleaseVersion }) if err != nil { - r.Log.Error(err, "failed to update DSCInitialization status after successfully completed reconciliation") - r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", "Failed to update DSCInitialization status") + r.Log.Error(err, "Failed to update DSCInitialization status after successfully completed reconciliation") + r.Recorder.Eventf(instance, corev1.EventTypeNormal, "DSCInitializationSuccessful", "Failed to update DSCInitialization status") } - return ctrl.Result{}, nil } } diff --git a/controllers/dscinitialization/servicemesh_setup.go b/controllers/dscinitialization/servicemesh_setup.go index 283a0f33570..5c6f8582620 100644 --- a/controllers/dscinitialization/servicemesh_setup.go +++ b/controllers/dscinitialization/servicemesh_setup.go @@ -42,7 +42,7 @@ func (r *DSCInitializationReconciler) configureServiceMesh(ctx context.Context, capabilityErr := capability.Apply(ctx) if capabilityErr != nil { r.Log.Error(capabilityErr, "failed applying service mesh resources") - r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", "failed applying service mesh resources") + r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", "Failed applying service mesh resources") return capabilityErr } } @@ -80,7 +80,7 @@ func (r *DSCInitializationReconciler) removeServiceMesh(ctx context.Context, ins capabilityErr := capability.Delete(ctx) if capabilityErr != nil { r.Log.Error(capabilityErr, "failed deleting service mesh resources") - r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", "failed deleting service mesh resources") + r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", "Failed deleting service mesh resources") return capabilityErr } diff --git a/controllers/status/component_condition.go b/controllers/status/component_condition.go new file mode 100644 index 00000000000..928c387963c --- /dev/null +++ b/controllers/status/component_condition.go @@ -0,0 +1,66 @@ +package status + +import ( + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" + corev1 "k8s.io/api/core/v1" +) + +// Component init condition when it is just created +// type: Ready +// status: Unknown +// reason: ReconcileStart +func NewComponentCondition(conditions *[]conditionsv1.Condition, componentName string, enabled bool) { + managementstatue := "not Managed" + if enabled { + managementstatue = "Managed" + } + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionType(componentName + PhaseReady), + Status: corev1.ConditionUnknown, + Reason: ReconcileStartReason, + Message: "Component managementStatus is " + managementstatue, + }) +} + +// Component reconilce success. +// type: Ready +// status: True +// reason: ReconcileCompleted +func SuccessComponentCondition(componentName string) conditionsv1.Condition { + return conditionsv1.Condition{ + Type: conditionsv1.ConditionType(componentName + PhaseReady), + Status: corev1.ConditionTrue, + Reason: ReconcileSuccessReason, + Message: "Component reconciled successfully", + } +} + +// Component reconcile failed. +// type: Ready +// status: False +// reason: ReconcileFailed +// message: +func FailedComponentCondition(componentName string, err error) (conditionsv1.Condition, error) { + FailedCondition := setFailedComponentCondition(componentName) + FailedCondition.Message = err.Error() + return FailedCondition, err +} + +// Component failed reconcile condition, called by FailedComponentCondition(). +func setFailedComponentCondition(componentName string) conditionsv1.Condition { + return conditionsv1.Condition{ + Type: conditionsv1.ConditionType(componentName + PhaseReady), + Status: corev1.ConditionFalse, + Reason: ReconcileFailedReason, + } +} + +// Special handling on DSPA for Argo. +func ArgoExistCondition(reason string, message string) conditionsv1.Condition { + return conditionsv1.Condition{ + Type: CapabilityDSPv2Argo, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + } +} diff --git a/controllers/status/const.go b/controllers/status/const.go new file mode 100644 index 00000000000..9ed8d828d07 --- /dev/null +++ b/controllers/status/const.go @@ -0,0 +1,81 @@ +/* +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 status contains different conditions, phases and progresses, +// being used by DataScienceCluster and DSCInitialization's controller +package status + +import ( + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" +) + +// Condition.Message. +const ( + DSCIReconcileStartMessage = "Initializing DSCInitialization resource" + DSCIMissingMessage = "Failed to get a valid DSCInitialization CR, please create DSCI instance" + DSCIReconcileFailedMessage = "Failed to reconcile DSCInitialization resource" + DSCIReconcileSuccessMessage = "DSCInitialization reconciled successfully" + + DSCReconcileStartMessage = "Initializing DataScienceCluster resource" + DSCReconcileSuccessMessage = "DataScienceCluster reconciled successfully" + TerminatingMessage = "Resource is being terminated" +) + +// Condition.Reason. +const ( + // DSPv2. + ArgoWorkflowExistReason string = "ArgoWorkflowExist" +) +const ( + // DSCI ServiceMesh + Authorino. + MissingOperatorReason string = "MissingOperator" + ConfiguredReason string = "Configured" + RemovedReason string = "Removed" + CapabilityFailed string = "CapabilityFailed" +) +const ( + // DSCI and DSC. + DSCIMissingReason = "MissingDSCI" + ReconcileStartReason = "ReconcileStart" + ReconcileSuccessReason = "ReconcileCompleted" + ReconcileFailedReason = "ReconcileFailed" + ReconcileCompletedWithComponentErrorsReason = "ReconcileCompletedWithComponentErrors" + // Remove. + TerminatingReason = "Terminating" + RemoveFailedReason = "RemoveFailed" +) + +// Condition.Type. +const ( + ConditionReconcileSuccess conditionsv1.ConditionType = "ReconcileSuccess" +) +const ( + // DSCI ServiceMesh + Authorino. + CapabilityServiceMesh conditionsv1.ConditionType = "CapabilityServiceMesh" + CapabilityServiceMeshAuthorization conditionsv1.ConditionType = "CapabilityServiceMeshAuthorization" +) +const ( + // component DSPv2. + CapabilityDSPv2Argo conditionsv1.ConditionType = "CapabilityDSPv2Argo" +) + +// Phase. +const ( + PhaseError = "Error" + PhaseReady = "Ready" + PhaseCreated = "Created" + PhaseDeleting = "Deleting" +) diff --git a/controllers/status/controller_condition.go b/controllers/status/controller_condition.go new file mode 100644 index 00000000000..665306a1d14 --- /dev/null +++ b/controllers/status/controller_condition.go @@ -0,0 +1,103 @@ +package status + +import ( + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" + corev1 "k8s.io/api/core/v1" +) + +// DSCI / DSC start(just created). +// type: Progressing +// statue: True +// Reason: ReconcileStart. +func InitControllerCondition(message string) *conditionsv1.Condition { + return &conditionsv1.Condition{ + Type: conditionsv1.ConditionProgressing, + Status: corev1.ConditionTrue, + Reason: ReconcileStartReason, + Message: message, + } +} + +// SetCompleteCondition sets all conditions to indicate reconciliation process has completed and successfully. +// type: Available|Progressing|Degraded|ReconcileSuccess +// status: True |False |False |True +func SetCompleteCondition(conditions *[]conditionsv1.Condition, reason string, message string) { + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: ConditionReconcileSuccess, + 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, + }) +} + +// SetErrorCondition sets all conditions to indicate reconciliation process will continue and it is in error. +// But the CR(DSCI and DSC) are available as it is functional +// type: Available|Progressing|Degraded|ReconcileSuccess +// status: True |True |True |False +func SetErrorCondition(conditions *[]conditionsv1.Condition, reason string, message string) { + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: ConditionReconcileSuccess, + Status: corev1.ConditionFalse, + 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.ConditionFalse, + Reason: reason, + Message: message, + }) + conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ + Type: conditionsv1.ConditionDegraded, + Status: corev1.ConditionTrue, + Reason: reason, + Message: message, + }) +} + +// UnavailableCondition sets available conditions to false to indicate resource is in terminating process. +// type: Available +// status: False +func UnavailableCondition(reason string, message string) *conditionsv1.Condition { + return &conditionsv1.Condition{ + Type: conditionsv1.ConditionAvailable, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + } +} + +// UpdateCondition is top caller in controllers to handle different case: init, error, success etc. +// newCondition is the condition to be updated from the existing conditions list. +func UpdateCondition(conditions *[]conditionsv1.Condition, newCondition conditionsv1.Condition) { + conditionsv1.SetStatusCondition(conditions, newCondition) +} + +// RemoveComponentCondition remove Condition Type from given component when component changed to "not managed". +func RemoveComponentCondition(conditions *[]conditionsv1.Condition, condType conditionsv1.ConditionType) { + conditionsv1.RemoveStatusCondition(conditions, condType) +} diff --git a/controllers/status/doc.go b/controllers/status/doc.go index 027d532219b..1c05317d1ee 100644 --- a/controllers/status/doc.go +++ b/controllers/status/doc.go @@ -40,7 +40,7 @@ // doServiceMeshStuff manages the Service Mesh configuration process during DSCInitialization reconcile. // It creates a reporter and reports any conditions derived from the service mesh configuration process. // -// func (r *DSCInitializationReconciler) doStdoServiceMeshStuffff(instance *dsciv1.DSCInitialization) error { +// func (r *DSCInitializationReconciler) doServiceMeshStuff(instance *dsciv1.DSCInitialization) error { // reporter := createReporter(r.Client, instance, &conditionsv1.Condition{ // Type: status.CapabilityServiceMesh, // Status: corev1.ConditionTrue, diff --git a/controllers/status/status.go b/controllers/status/status.go deleted file mode 100644 index 52e3d79145f..00000000000 --- a/controllers/status/status.go +++ /dev/null @@ -1,211 +0,0 @@ -/* -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 status contains different conditions, phases and progresses, -// being used by DataScienceCluster and DSCInitialization's controller -package status - -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. -const ( - // PhaseIgnored is used when a resource is ignored - // is an example of a constant that is not used anywhere in the code. - PhaseIgnored = "Ignored" - // PhaseNotReady is used when waiting for system to be ready after reconcile is successful - // is an example of a constant that is not used anywhere in the code. - PhaseNotReady = "Not Ready" - // PhaseClusterExpanding is used when cluster is expanding capacity - // is an example of a constant that is not used anywhere in the code. - PhaseClusterExpanding = "Expanding Capacity" - // PhaseDeleting is used when cluster is deleting - // is an example of a constant that is not used anywhere in the code. - PhaseDeleting = "Deleting" - // PhaseConnecting is used when cluster is connecting to external cluster - // is an example of a constant that is not used anywhere in the code. - PhaseConnecting = "Connecting" - // PhaseOnboarding is used when consumer is Onboarding - // is an example of a constant that is not used anywhere in the code. - PhaseOnboarding = "Onboarding" - - // 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" -) - -// List of constants to show different reconciliation messages and statuses. -const ( - // ReconcileFailed is used when multiple DSCI instance exists or DSC reconcile failed/removal failed. - ReconcileFailed = "ReconcileFailed" - ReconcileInit = "ReconcileInit" - ReconcileCompleted = "ReconcileCompleted" - ReconcileCompletedWithComponentErrors = "ReconcileCompletedWithComponentErrors" - ReconcileCompletedMessage = "Reconcile completed successfully" - - // ConditionReconcileComplete represents extra Condition Type, used by .Condition.Type. - ConditionReconcileComplete conditionsv1.ConditionType = "ReconcileComplete" -) - -const ( - CapabilityServiceMesh conditionsv1.ConditionType = "CapabilityServiceMesh" - CapabilityServiceMeshAuthorization conditionsv1.ConditionType = "CapabilityServiceMeshAuthorization" - CapabilityDSPv2Argo conditionsv1.ConditionType = "CapabilityDSPv2Argo" -) - -const ( - MissingOperatorReason string = "MissingOperator" - ConfiguredReason string = "Configured" - RemovedReason string = "Removed" - CapabilityFailed string = "CapabilityFailed" - ArgoWorkflowExist string = "ArgoWorkflowExist" -) - -const ( - ReadySuffix = "Ready" -) - -// 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, - }) - 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.ConditionFalse, - Reason: reason, - Message: message, - }) - conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ - Type: conditionsv1.ConditionDegraded, - Status: corev1.ConditionTrue, - Reason: reason, - Message: message, - }) - conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ - Type: conditionsv1.ConditionUpgradeable, - 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, - }) - conditionsv1.RemoveStatusCondition(conditions, CapabilityDSPv2Argo) -} - -// SetCondition is a general purpose function to update any type of condition. -func SetCondition(conditions *[]conditionsv1.Condition, conditionType string, reason string, message string, status corev1.ConditionStatus) { - conditionsv1.SetStatusCondition(conditions, conditionsv1.Condition{ - Type: conditionsv1.ConditionType(conditionType), - Status: status, - Reason: reason, - Message: message, - }) -} - -// SetComponentCondition appends Condition Type with const ReadySuffix for given component -// when component finished reconcile. -func SetComponentCondition(conditions *[]conditionsv1.Condition, component string, reason string, message string, status corev1.ConditionStatus) { - SetCondition(conditions, component+ReadySuffix, reason, message, status) -} - -// RemoveComponentCondition remove Condition of giving component. -func RemoveComponentCondition(conditions *[]conditionsv1.Condition, component string) { - conditionsv1.RemoveStatusCondition(conditions, conditionsv1.ConditionType(component+ReadySuffix)) -} diff --git a/docs/api-overview.md b/docs/api-overview.md index c909cee2b11..fb9cde52dfc 100644 --- a/docs/api-overview.md +++ b/docs/api-overview.md @@ -480,8 +480,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `phase` _string_ | Phase describes the Phase of DataScienceCluster reconciliation state
This is used by OLM UI to provide status information to the user | | | -| `conditions` _Condition array_ | Conditions describes the state of the DataScienceCluster resource. | | | +| `phase` _string_ | Phase describes the Phase of DataScienceCluster reconciliation state
This is used by OLM UI to provide status information to the user
Newer API types should use conditions instead. Phase was essentially a state-machine enumeration field, that contradicted system-design principles and hampered evolution, since adding new enum values breaks backward compatibility.
Rather than encouraging clients to infer implicit properties from phases, we prefer to explicitly expose the individual conditions that clients need to monitor.
Known .status.phase are: "Created", "Error", "Ready" "Deleting" | | | +| `conditions` _Condition array_ | Conditions describes the state of the DataScienceCluster resource.
standard known .status.conditions.type are: "Available", "Progressing", "Degraded"
Extra .status.conditions.type are : "ReconcileSuccess" "CapabilityDSPv2Argo" and Ready | | | | `relatedObjects` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectreference-v1-core) array_ | RelatedObjects is a list of objects created and maintained by this operator.
Object references will be added to this list after they have been created AND found in the cluster. | | | | `errorMessage` _string_ | | | | | `installedComponents` _object (keys:string, values:boolean)_ | List of components with status if installed or not | | | @@ -629,8 +629,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `phase` _string_ | Phase describes the Phase of DSCInitializationStatus
This is used by OLM UI to provide status information to the user | | | -| `conditions` _Condition array_ | Conditions describes the state of the DSCInitializationStatus resource | | | +| `phase` _string_ | Phase describes the Phase of DSCInitializationStatus
This is used by OLM UI to provide status information to the user
The pattern of using phase is deprecated.
Newer API types should use conditions instead. Phase was essentially a state-machine enumeration field, that contradicted system-design principles and hampered evolution, since adding new enum values breaks backward compatibility.
Rather than encouraging clients to infer implicit properties from phases, we prefer to explicitly expose the individual conditions that clients need to monitor.
Known .status.phase are: "Created", "Error", "Ready" "Deleting" | | | +| `conditions` _Condition array_ | Conditions describes the state of the DSCInitializationStatus resource
standard known .status.conditions.type are: "Available", "Progressing", "Degraded"
Extra .status.conditions.type are : "ReconcileSuccess", "CapabilityServiceMesh", "CapabilityServiceMeshAuthorization" | | | | `relatedObjects` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectreference-v1-core) array_ | RelatedObjects is a list of objects created and maintained by this operator.
Object references will be added to this list after they have been created AND found in the cluster | | | | `errorMessage` _string_ | | | | | `release` _[Release](#release)_ | Version and release type | | | diff --git a/pkg/feature/feature.go b/pkg/feature/feature.go index 967e284cacc..dfb24bae11b 100644 --- a/pkg/feature/feature.go +++ b/pkg/feature/feature.go @@ -2,10 +2,11 @@ package feature import ( "context" - "fmt" "github.com/go-logr/logr" "github.com/hashicorp/go-multierror" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -89,8 +90,14 @@ func (f *Feature) Apply(ctx context.Context) error { } if _, updateErr := status.UpdateWithRetry(ctx, f.Client, f.tracker, func(saved *featurev1.FeatureTracker) { - status.SetProgressingCondition(&saved.Status.Conditions, string(featurev1.ConditionReason.FeatureCreated), fmt.Sprintf("Applying feature [%s]", f.Name)) - saved.Status.Phase = status.PhaseProgressing + featureCondition := conditionsv1.Condition{ + Type: conditionsv1.ConditionProgressing, + Status: corev1.ConditionTrue, + Reason: string(featurev1.ConditionReason.FeatureCreated), + Message: "Applying feature " + f.Name, + } + conditionsv1.SetStatusCondition(&saved.Status.Conditions, featureCondition) + saved.Status.Phase = string(conditionsv1.ConditionProgressing) }); updateErr != nil { return updateErr } diff --git a/pkg/upgrade/uninstallation.go b/pkg/upgrade/uninstallation.go index d31cd43c1f2..4411dca574b 100644 --- a/pkg/upgrade/uninstallation.go +++ b/pkg/upgrade/uninstallation.go @@ -44,6 +44,7 @@ func OperatorUninstall(ctx context.Context, cli client.Client) error { } // Return if any one of the namespaces is Terminating due to resources that are in process of deletion. (e.g. CRDs) + // due to missing .status.conditions in namespace, only .status.phase is available for check for _, namespace := range generatedNamespaces.Items { if namespace.Status.Phase == corev1.NamespaceTerminating { return fmt.Errorf("waiting for namespace %v to be deleted", namespace.Name)