diff --git a/internal/knowledge/datasources/openstack/controller.go b/internal/knowledge/datasources/openstack/controller.go index eb4da8d6..7806ab42 100644 --- a/internal/knowledge/datasources/openstack/controller.go +++ b/internal/knowledge/datasources/openstack/controller.go @@ -71,14 +71,16 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl. FromSecretRef(ctx, datasource.Spec.DatabaseSecretRef) if err != nil { log.Error(err, "failed to authenticate with database", "secretRef", datasource.Spec.DatabaseSecretRef) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "DatabaseAuthenticationFailed", Message: "failed to authenticate with database: " + err.Error(), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err @@ -91,14 +93,16 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl. FromSecretRef(ctx, *datasource.Spec.SSOSecretRef) if err != nil { log.Error(err, "failed to authenticate with SSO", "secretRef", datasource.Spec.SSOSecretRef) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "SSOAuthenticationFailed", Message: "failed to authenticate with SSO: " + err.Error(), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err @@ -110,14 +114,16 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl. FromSecretRef(ctx, datasource.Spec.OpenStack.SecretRef) if err != nil { log.Error(err, "failed to authenticate with keystone", "secretRef", datasource.Spec.OpenStack.SecretRef) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "KeystoneAuthenticationFailed", Message: "failed to authenticate with keystone: " + err.Error(), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err @@ -131,14 +137,16 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl. ) if err != nil { log.Info("skipping datasource, unsupported openstack datasource type", "type", datasource.Spec.OpenStack.Type) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "UnsupportedOpenStackDatasourceType", Message: "unsupported openstack datasource type: " + string(datasource.Spec.OpenStack.Type), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -147,14 +155,16 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl. // Initialize the syncer before syncing. if err := syncer.Init(ctx); err != nil { log.Error(err, "failed to init openstack datasource", "name", datasource.Name) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "OpenStackDatasourceInitFailed", Message: "failed to init openstack datasource: " + err.Error(), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err @@ -163,14 +173,16 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl. nResults, err := syncer.Sync(ctx) if errors.Is(err, v1alpha1.ErrWaitingForDependencyDatasource) { log.Info("datasource sync waiting for dependency datasource", "name", datasource.Name) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionWaiting, Status: metav1.ConditionTrue, Reason: "WaitingForDependencyDatasource", Message: "waiting for dependency datasource", }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } // Requeue after a short delay to check again. @@ -179,20 +191,23 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl. // Other error if err != nil { log.Error(err, "failed to sync openstack datasource", "name", datasource.Name) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "OpenStackDatasourceSyncFailed", Message: "failed to sync openstack datasource: " + err.Error(), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err } // Update the datasource status to reflect successful sync. + old := datasource.DeepCopy() meta.RemoveStatusCondition(&datasource.Status.Conditions, v1alpha1.DatasourceConditionError) meta.RemoveStatusCondition(&datasource.Status.Conditions, v1alpha1.DatasourceConditionWaiting) datasource.Status.LastSynced = metav1.NewTime(time.Now()) @@ -200,8 +215,9 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl. datasource.Status.NextSyncTime = metav1.NewTime(nextTime) datasource.Status.NumberOfObjects = nResults datasource.Status.Took = metav1.Duration{Duration: time.Since(startedAt)} - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } diff --git a/internal/knowledge/datasources/prometheus/controller.go b/internal/knowledge/datasources/prometheus/controller.go index 6c612da2..e9bd82d3 100644 --- a/internal/knowledge/datasources/prometheus/controller.go +++ b/internal/knowledge/datasources/prometheus/controller.go @@ -61,14 +61,16 @@ func (r *PrometheusDatasourceReconciler) Reconcile(ctx context.Context, req ctrl newSyncerFunc, ok := supportedMetricSyncers[datasource.Spec.Prometheus.Type] if !ok { log.Info("skipping datasource, unsupported metric type", "metricType", datasource.Spec.Prometheus.Type) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "UnsupportedPrometheusMetricType", Message: "unsupported metric type: " + datasource.Spec.Prometheus.Type, }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -79,14 +81,16 @@ func (r *PrometheusDatasourceReconciler) Reconcile(ctx context.Context, req ctrl FromSecretRef(ctx, datasource.Spec.DatabaseSecretRef) if err != nil { log.Error(err, "failed to authenticate with database", "secretRef", datasource.Spec.DatabaseSecretRef) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "DatabaseAuthenticationFailed", Message: "failed to authenticate with database: " + err.Error(), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err @@ -99,14 +103,16 @@ func (r *PrometheusDatasourceReconciler) Reconcile(ctx context.Context, req ctrl FromSecretRef(ctx, *datasource.Spec.SSOSecretRef) if err != nil { log.Error(err, "failed to authenticate with SSO", "secretRef", datasource.Spec.SSOSecretRef) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "SSOAuthenticationFailed", Message: "failed to authenticate with SSO: " + err.Error(), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err @@ -124,14 +130,16 @@ func (r *PrometheusDatasourceReconciler) Reconcile(ctx context.Context, req ctrl prometheusURL, ok := secret.Data["url"] if !ok { log.Error(err, "missing 'url' in prometheus secret", "secretRef", datasource.Spec.Prometheus.SecretRef) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "MissingPrometheusURL", Message: "missing 'url' in prometheus secret", }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err @@ -147,27 +155,31 @@ func (r *PrometheusDatasourceReconciler) Reconcile(ctx context.Context, req ctrl nResults, nextSync, err := syncer.Sync(ctx) if err != nil { log.Error(err, "failed to sync prometheus datasource", "name", datasource.Name) + old := datasource.DeepCopy() meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{ Type: v1alpha1.DatasourceConditionError, Status: metav1.ConditionTrue, Reason: "PrometheusDatasourceSyncFailed", Message: "failed to sync prometheus datasource: " + err.Error(), }) - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } return ctrl.Result{}, err } // Update the datasource status to reflect successful sync. + old := datasource.DeepCopy() meta.RemoveStatusCondition(&datasource.Status.Conditions, v1alpha1.DatasourceConditionError) datasource.Status.LastSynced = metav1.NewTime(time.Now()) datasource.Status.NextSyncTime = metav1.NewTime(nextSync) datasource.Status.NumberOfObjects = nResults datasource.Status.Took = metav1.Duration{Duration: time.Since(startedAt)} - if err := r.Status().Update(ctx, datasource); err != nil { - log.Error(err, "failed to update datasource status", "name", datasource.Name) + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, datasource, patch); err != nil { + log.Error(err, "failed to patch datasource status", "name", datasource.Name) return ctrl.Result{}, err } diff --git a/internal/knowledge/extractor/controller.go b/internal/knowledge/extractor/controller.go index cb7cbad5..f212832c 100644 --- a/internal/knowledge/extractor/controller.go +++ b/internal/knowledge/extractor/controller.go @@ -56,14 +56,16 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( extractor, ok := supportedExtractors[knowledge.Spec.Extractor.Name] if !ok { log.Info("skipping knowledge extraction, unsupported extractor", "name", knowledge.Spec.Extractor.Name) + old := knowledge.DeepCopy() meta.SetStatusCondition(&knowledge.Status.Conditions, metav1.Condition{ Type: v1alpha1.KnowledgeConditionError, Status: metav1.ConditionTrue, Reason: "UnsupportedExtractor", Message: "unsupported extractor name: " + knowledge.Spec.Extractor.Name, }) - if err := r.Status().Update(ctx, knowledge); err != nil { - log.Error(err, "failed to update knowledge status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -78,14 +80,16 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( Name: dsRef.Name, }, ds); err != nil { log.Error(err, "failed to get datasource", "name", dsRef.Name) + old := knowledge.DeepCopy() meta.SetStatusCondition(&knowledge.Status.Conditions, metav1.Condition{ Type: v1alpha1.KnowledgeConditionError, Status: metav1.ConditionTrue, Reason: "DatasourceFetchFailed", Message: "failed to get datasource: " + err.Error(), }) - if err := r.Status().Update(ctx, knowledge); err != nil { - log.Error(err, "failed to update knowledge status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge status") return ctrl.Result{}, err } return ctrl.Result{}, err @@ -95,14 +99,16 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } else if databaseSecretRef.Name != ds.Spec.DatabaseSecretRef.Name || databaseSecretRef.Namespace != ds.Spec.DatabaseSecretRef.Namespace { log.Error(nil, "datasources have differing database secret refs") + old := knowledge.DeepCopy() meta.SetStatusCondition(&knowledge.Status.Conditions, metav1.Condition{ Type: v1alpha1.KnowledgeConditionError, Status: metav1.ConditionTrue, Reason: "InconsistentDatabaseSecretRefs", Message: "datasources have differing database secret refs", }) - if err := r.Status().Update(ctx, knowledge); err != nil { - log.Error(err, "failed to update knowledge status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -116,14 +122,16 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( FromSecretRef(ctx, *databaseSecretRef) if err != nil { log.Error(err, "failed to authenticate with database", "secretRef", *databaseSecretRef) + old := knowledge.DeepCopy() meta.SetStatusCondition(&knowledge.Status.Conditions, metav1.Condition{ Type: v1alpha1.KnowledgeConditionError, Status: metav1.ConditionTrue, Reason: "DatabaseAuthenticationFailed", Message: "failed to authenticate with database: " + err.Error(), }) - if err := r.Status().Update(ctx, knowledge); err != nil { - log.Error(err, "failed to update knowledge status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge status") return ctrl.Result{}, err } return ctrl.Result{}, err @@ -134,14 +142,16 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( wrapped := monitorFeatureExtractor(knowledge.Spec.Extractor.Name, extractor, r.Monitor) if err := wrapped.Init(authenticatedDatasourceDB, r.Client, knowledge.Spec); err != nil { log.Error(err, "failed to initialize feature extractor", "name", knowledge.Spec) + old := knowledge.DeepCopy() meta.SetStatusCondition(&knowledge.Status.Conditions, metav1.Condition{ Type: v1alpha1.KnowledgeConditionError, Status: metav1.ConditionTrue, Reason: "FeatureExtractorInitializationFailed", Message: "failed to initialize feature extractor: " + err.Error(), }) - if err := r.Status().Update(ctx, knowledge); err != nil { - log.Error(err, "failed to update knowledge status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge status") return ctrl.Result{}, err } return ctrl.Result{}, err @@ -150,20 +160,23 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( features, err := extractor.Extract() if err != nil { log.Error(err, "failed to extract features", "name", knowledge.Spec.Extractor.Name) + old := knowledge.DeepCopy() meta.SetStatusCondition(&knowledge.Status.Conditions, metav1.Condition{ Type: v1alpha1.KnowledgeConditionError, Status: metav1.ConditionTrue, Reason: "FeatureExtractionFailed", Message: "failed to extract features: " + err.Error(), }) - if err := r.Status().Update(ctx, knowledge); err != nil { - log.Error(err, "failed to update knowledge status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge status") return ctrl.Result{}, err } return ctrl.Result{}, err } // Update the knowledge status. + old := knowledge.DeepCopy() meta.RemoveStatusCondition(&knowledge.Status.Conditions, v1alpha1.KnowledgeConditionError) raw, err := v1alpha1.BoxFeatureList(features) if err != nil { @@ -174,8 +187,9 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( Reason: "FeatureMarshalingFailed", Message: "failed to marshal extracted features: " + err.Error(), }) - if err := r.Status().Update(ctx, knowledge); err != nil { - log.Error(err, "failed to update knowledge status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge status") return ctrl.Result{}, err } return ctrl.Result{}, err @@ -184,8 +198,9 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( knowledge.Status.LastExtracted = metav1.NewTime(time.Now()) knowledge.Status.RawLength = len(features) knowledge.Status.Took = metav1.Duration{Duration: time.Since(startedAt)} - if err := r.Status().Update(ctx, knowledge); err != nil { - log.Error(err, "failed to update knowledge status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge status") return ctrl.Result{}, err } log.Info("successfully extracted knowledge", "name", knowledge.Name, "took", knowledge.Status.Took.Duration) diff --git a/internal/knowledge/extractor/trigger.go b/internal/knowledge/extractor/trigger.go index 818f0998..95391a06 100644 --- a/internal/knowledge/extractor/trigger.go +++ b/internal/knowledge/extractor/trigger.go @@ -173,14 +173,16 @@ func (r *TriggerReconciler) enqueueKnowledgeReconciliation(ctx context.Context, // The controller-runtime framework will automatically handle the delayed reconciliation // We update the knowledge annotation to trigger reconciliation by the main KnowledgeReconciler + old := knowledge.DeepCopy() if knowledge.Annotations == nil { knowledge.Annotations = make(map[string]string) } // Add a trigger annotation with current timestamp to force reconciliation knowledge.Annotations["cortex.knowledge/trigger-reconciliation"] = time.Now().Format(time.RFC3339) - if err := r.Update(ctx, &knowledge); err != nil { - log.Error(err, "failed to update knowledge to trigger reconciliation", "knowledge", knowledge.Name) + patch := client.MergeFrom(old) + if err := r.Patch(ctx, &knowledge, patch); err != nil { + log.Error(err, "failed to patch knowledge to trigger reconciliation", "knowledge", knowledge.Name) return err } log.Info( diff --git a/internal/knowledge/kpis/controller.go b/internal/knowledge/kpis/controller.go index 9f4bbd46..b361893c 100644 --- a/internal/knowledge/kpis/controller.go +++ b/internal/knowledge/kpis/controller.go @@ -73,6 +73,7 @@ func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } // Reconcile the kpi. + old := kpi.DeepCopy() err := c.handleKPIChange(ctx, kpi) if err != nil { meta.SetStatusCondition(&kpi.Status.Conditions, metav1.Condition{ @@ -84,8 +85,9 @@ func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } else { meta.RemoveStatusCondition(&kpi.Status.Conditions, v1alpha1.KPIConditionError) } - if err := c.Status().Update(ctx, kpi); err != nil { - log.Error(err, "failed to update kpi status after reconciliation error", "name", kpi.Name) + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, kpi, patch); err != nil { + log.Error(err, "failed to patch kpi status after reconciliation error", "name", kpi.Name) } return ctrl.Result{}, nil } @@ -104,6 +106,7 @@ func (c *Controller) InitAllKPIs(ctx context.Context) error { if kpi.Spec.SchedulingDomain != c.SchedulingDomain { continue } + old := kpi.DeepCopy() err := c.handleKPIChange(ctx, &kpi) if err != nil { meta.SetStatusCondition(&kpi.Status.Conditions, metav1.Condition{ @@ -115,8 +118,9 @@ func (c *Controller) InitAllKPIs(ctx context.Context) error { } else { meta.RemoveStatusCondition(&kpi.Status.Conditions, v1alpha1.KPIConditionError) } - if err := c.Status().Update(ctx, &kpi); err != nil { - log.Error(err, "failed to update kpi status after reconciliation error", "name", kpi.Name) + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, &kpi, patch); err != nil { + log.Error(err, "failed to patch kpi status after reconciliation error", "name", kpi.Name) } } return nil diff --git a/internal/knowledge/kpis/controller_test.go b/internal/knowledge/kpis/controller_test.go index 66e157a3..77e66784 100644 --- a/internal/knowledge/kpis/controller_test.go +++ b/internal/knowledge/kpis/controller_test.go @@ -148,6 +148,7 @@ func (mc *mockController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } // Reconcile the kpi using mock method. + old := kpi.DeepCopy() err := mc.handleKPIChange(ctx, kpi) if err != nil { meta.SetStatusCondition(&kpi.Status.Conditions, metav1.Condition{ @@ -159,8 +160,9 @@ func (mc *mockController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } else { meta.RemoveStatusCondition(&kpi.Status.Conditions, v1alpha1.KPIConditionError) } - if err := mc.Status().Update(ctx, kpi); err != nil { - log.Error(err, "failed to update kpi status after reconciliation error", "name", kpi.Name) + patch := client.MergeFrom(old) + if err := mc.Status().Patch(ctx, kpi, patch); err != nil { + log.Error(err, "failed to patch kpi status after reconciliation error", "name", kpi.Name) } return ctrl.Result{}, nil } diff --git a/internal/scheduling/decisions/cinder/pipeline_controller.go b/internal/scheduling/decisions/cinder/pipeline_controller.go index 7220897b..7de976f7 100644 --- a/internal/scheduling/decisions/cinder/pipeline_controller.go +++ b/internal/scheduling/decisions/cinder/pipeline_controller.go @@ -60,10 +60,12 @@ func (c *DecisionPipelineController) Reconcile(ctx context.Context, req ctrl.Req if err := c.Get(ctx, req.NamespacedName, decision); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return ctrl.Result{}, err } - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -83,11 +85,13 @@ func (c *DecisionPipelineController) ProcessNewDecisionFromAPI(ctx context.Conte return err } } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return err } if pipelineConf.Spec.CreateDecisions { - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return err } } diff --git a/internal/scheduling/decisions/explanation/controller.go b/internal/scheduling/decisions/explanation/controller.go index 3b750528..c424d590 100644 --- a/internal/scheduling/decisions/explanation/controller.go +++ b/internal/scheduling/decisions/explanation/controller.go @@ -125,11 +125,13 @@ func (c *Controller) reconcileHistory(ctx context.Context, decision *v1alpha1.De UID: prevDecision.UID, }) } + old := decision.DeepCopy() decision.Status.History = &history precedence := len(history) decision.Status.Precedence = &precedence - if err := c.Status().Update(ctx, decision); err != nil { - log.Error(err, "failed to update decision status with history", "name", decision.Name) + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { + log.Error(err, "failed to patch decision status with history", "name", decision.Name) return err } log.Info("successfully reconciled decision history", "name", decision.Name) @@ -149,9 +151,11 @@ func (c *Controller) reconcileExplanation(ctx context.Context, decision *v1alpha log.Error(err, "failed to explain decision", "name", decision.Name) return err } + old := decision.DeepCopy() decision.Status.Explanation = explanationText - if err := c.Status().Update(ctx, decision); err != nil { - log.Error(err, "failed to update decision status with explanation", "name", decision.Name) + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { + log.Error(err, "failed to patch decision status with explanation", "name", decision.Name) return err } log.Info("successfully reconciled decision explanation", "name", decision.Name) diff --git a/internal/scheduling/decisions/machines/pipeline_controller.go b/internal/scheduling/decisions/machines/pipeline_controller.go index 2e372bf8..14ddc35f 100644 --- a/internal/scheduling/decisions/machines/pipeline_controller.go +++ b/internal/scheduling/decisions/machines/pipeline_controller.go @@ -64,10 +64,12 @@ func (c *DecisionPipelineController) Reconcile(ctx context.Context, req ctrl.Req if err := c.Get(ctx, req.NamespacedName, decision); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return ctrl.Result{}, err } - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -104,11 +106,13 @@ func (c *DecisionPipelineController) ProcessNewMachine(ctx context.Context, mach return err } } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return err } if pipelineConf.Spec.CreateDecisions { - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return err } } @@ -155,8 +159,10 @@ func (c *DecisionPipelineController) process(ctx context.Context, decision *v1al return err } // Assign the first machine pool returned by the pipeline. + old := machine.DeepCopy() machine.Spec.MachinePoolRef = &corev1.LocalObjectReference{Name: *result.TargetHost} - if err := c.Update(ctx, machine); err != nil { + patch := client.MergeFrom(old) + if err := c.Patch(ctx, machine, patch); err != nil { log.V(1).Error(err, "failed to assign machine pool to instance") return err } diff --git a/internal/scheduling/decisions/manila/pipeline_controller.go b/internal/scheduling/decisions/manila/pipeline_controller.go index d3537c68..06451586 100644 --- a/internal/scheduling/decisions/manila/pipeline_controller.go +++ b/internal/scheduling/decisions/manila/pipeline_controller.go @@ -60,10 +60,12 @@ func (c *DecisionPipelineController) Reconcile(ctx context.Context, req ctrl.Req if err := c.Get(ctx, req.NamespacedName, decision); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return ctrl.Result{}, err } - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -83,11 +85,13 @@ func (c *DecisionPipelineController) ProcessNewDecisionFromAPI(ctx context.Conte return err } } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return err } if pipelineConf.Spec.CreateDecisions { - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return err } } diff --git a/internal/scheduling/decisions/nova/pipeline_controller.go b/internal/scheduling/decisions/nova/pipeline_controller.go index ff8fca9a..68bdd6c7 100644 --- a/internal/scheduling/decisions/nova/pipeline_controller.go +++ b/internal/scheduling/decisions/nova/pipeline_controller.go @@ -61,10 +61,12 @@ func (c *DecisionPipelineController) Reconcile(ctx context.Context, req ctrl.Req if err := c.Get(ctx, req.NamespacedName, decision); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return ctrl.Result{}, err } - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -84,11 +86,13 @@ func (c *DecisionPipelineController) ProcessNewDecisionFromAPI(ctx context.Conte return err } } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return err } if pipelineConf.Spec.CreateDecisions { - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return err } } diff --git a/internal/scheduling/decisions/pods/pipeline_controller.go b/internal/scheduling/decisions/pods/pipeline_controller.go index 22cccc69..a969a36c 100644 --- a/internal/scheduling/decisions/pods/pipeline_controller.go +++ b/internal/scheduling/decisions/pods/pipeline_controller.go @@ -63,10 +63,12 @@ func (c *DecisionPipelineController) Reconcile(ctx context.Context, req ctrl.Req if err := c.Get(ctx, req.NamespacedName, decision); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return ctrl.Result{}, err } - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -103,11 +105,13 @@ func (c *DecisionPipelineController) ProcessNewPod(ctx context.Context, pod *cor return err } } + old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return err } if pipelineConf.Spec.CreateDecisions { - if err := c.Status().Update(ctx, decision); err != nil { + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, decision, patch); err != nil { return err } } diff --git a/internal/scheduling/descheduling/nova/executor.go b/internal/scheduling/descheduling/nova/executor.go index a01b25e4..fe590fcc 100644 --- a/internal/scheduling/descheduling/nova/executor.go +++ b/internal/scheduling/descheduling/nova/executor.go @@ -51,6 +51,7 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result // Currently we only know how to handle deschedulings for nova VMs. if descheduling.Spec.RefType != v1alpha1.DeschedulingSpecVMReferenceNovaServerUUID { log.Info("skipping descheduling, unsupported refType", "refType", descheduling.Spec.RefType) + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseFailed meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{ Type: v1alpha1.DeschedulingConditionError, @@ -58,8 +59,9 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result Reason: "UnsupportedRefType", Message: "unsupported refType: " + string(descheduling.Spec.RefType), }) - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -68,6 +70,7 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result // Currently we only know how to handle deschedulings for nova compute hosts. if descheduling.Spec.PrevHostType != v1alpha1.DeschedulingSpecHostTypeNovaComputeHostName { log.Info("skipping descheduling, unsupported prevHostType", "prevHostType", descheduling.Spec.PrevHostType) + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseFailed meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{ Type: v1alpha1.DeschedulingConditionError, @@ -75,8 +78,9 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result Reason: "UnsupportedPrevHostType", Message: "unsupported prevHostType: " + string(descheduling.Spec.PrevHostType), }) - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -85,6 +89,7 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result // We need a server uuid to proceed. if descheduling.Spec.Ref == "" { log.Info("skipping descheduling, missing ref") + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseFailed meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{ Type: v1alpha1.DeschedulingConditionError, @@ -92,8 +97,9 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result Reason: "MissingRef", Message: "missing ref", }) - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -120,6 +126,7 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result // Don't touch servers which don't match the provided previous host. if descheduling.Spec.PrevHost != "" && server.ComputeHost != descheduling.Spec.PrevHost { log.Error(errors.New("VM not on expected host"), "skipping descheduling, VM not on expected host", "vmId", vmId, "expectedHost", descheduling.Spec.PrevHost, "actualHost", server.ComputeHost) + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseFailed meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{ Type: v1alpha1.DeschedulingConditionError, @@ -127,8 +134,9 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result Reason: "VMNotOnExpectedHost", Message: "VM not on expected host, expected: " + descheduling.Spec.PrevHost + ", actual: " + server.ComputeHost, }) - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -137,6 +145,7 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result // Don't touch servers that are turned off or are in an error state. if server.Status != "ACTIVE" { log.Error(errors.New("VM not active"), "skipping descheduling, VM not active", "vmId", vmId) + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseFailed meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{ Type: v1alpha1.DeschedulingConditionError, @@ -144,8 +153,9 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result Reason: "VMNotActive", Message: "VM not active, current status: " + server.Status, }) - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -163,6 +173,7 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result log.Info("descheduler: executing migration for VM", "vmId", vmId) if err := e.NovaAPI.LiveMigrate(ctx, vmId); err != nil { log.Error(err, "descheduler: failed to live-migrate VM", "vmId", vmId, "error", err) + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseFailed meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{ Type: v1alpha1.DeschedulingConditionError, @@ -170,8 +181,9 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result Reason: "LiveMigrationFailed", Message: "failed to live-migrate VM: " + err.Error(), }) - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -183,6 +195,7 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result if err != nil { log.Error(err, "descheduler: failed to get VM status", "vmId", vmId) // Consider migration as failed + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseFailed meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{ Type: v1alpha1.DeschedulingConditionError, @@ -190,8 +203,9 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result Reason: "GetVMStatusFailed", Message: "failed to get VM status: " + err.Error(), }) - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -205,6 +219,7 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result } if server.Status == "ERROR" { log.Error(errors.New("live-migration failed for VM "+vmId), "descheduler: live-migration failed", "vmId", vmId) + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseFailed meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{ Type: v1alpha1.DeschedulingConditionError, @@ -212,8 +227,9 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result Reason: "LiveMigrationFailed", Message: "live-migration failed for VM: " + vmId, }) - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -225,12 +241,14 @@ func (e *Executor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result time.Sleep(jobloop.DefaultJitter(time.Second)) } + old := descheduling.DeepCopy() descheduling.Status.Phase = v1alpha1.DeschedulingStatusPhaseCompleted meta.RemoveStatusCondition(&descheduling.Status.Conditions, v1alpha1.DeschedulingConditionError) descheduling.Status.NewHost = server.ComputeHost descheduling.Status.NewHostType = v1alpha1.DeschedulingSpecHostTypeNovaComputeHostName - if err := e.Status().Update(ctx, descheduling); err != nil { - log.Error(err, "failed to update descheduling status") + patch := client.MergeFrom(old) + if err := e.Status().Patch(ctx, descheduling, patch); err != nil { + log.Error(err, "failed to patch descheduling status") return ctrl.Result{}, err } return ctrl.Result{}, nil diff --git a/internal/scheduling/lib/pipeline_controller.go b/internal/scheduling/lib/pipeline_controller.go index a6cf4d88..a96f2ee2 100644 --- a/internal/scheduling/lib/pipeline_controller.go +++ b/internal/scheduling/lib/pipeline_controller.go @@ -86,6 +86,7 @@ func (c *BasePipelineController[PipelineType]) handlePipelineChange( return } log := ctrl.LoggerFrom(ctx) + old := obj.DeepCopy() // Get all configured steps for the pipeline. var steps []v1alpha1.Step obj.Status.TotalSteps, obj.Status.ReadySteps = len(obj.Spec.Steps), 0 @@ -120,8 +121,9 @@ func (c *BasePipelineController[PipelineType]) handlePipelineChange( Reason: "StepNotReady", Message: err.Error(), }) - if err := c.Status().Update(ctx, obj); err != nil { - log.Error(err, "failed to update pipeline status", "pipelineName", obj.Name) + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, obj, patch); err != nil { + log.Error(err, "failed to patch pipeline status", "pipelineName", obj.Name) } delete(c.Pipelines, obj.Name) delete(c.PipelineConfigs, obj.Name) @@ -138,8 +140,9 @@ func (c *BasePipelineController[PipelineType]) handlePipelineChange( Reason: "PipelineInitFailed", Message: err.Error(), }) - if err := c.Status().Update(ctx, obj); err != nil { - log.Error(err, "failed to update pipeline status", "pipelineName", obj.Name) + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, obj, patch); err != nil { + log.Error(err, "failed to patch pipeline status", "pipelineName", obj.Name) } delete(c.Pipelines, obj.Name) delete(c.PipelineConfigs, obj.Name) @@ -148,8 +151,9 @@ func (c *BasePipelineController[PipelineType]) handlePipelineChange( log.Info("pipeline created and ready", "pipelineName", obj.Name) obj.Status.Ready = true meta.RemoveStatusCondition(&obj.Status.Conditions, v1alpha1.PipelineConditionError) - if err := c.Status().Update(ctx, obj); err != nil { - log.Error(err, "failed to update pipeline status", "pipelineName", obj.Name) + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, obj, patch); err != nil { + log.Error(err, "failed to patch pipeline status", "pipelineName", obj.Name) return } } @@ -208,6 +212,7 @@ func (c *BasePipelineController[PipelineType]) handleStepChange( } log := ctrl.LoggerFrom(ctx) // Check the status of all knowledges depending on this step. + old := obj.DeepCopy() obj.Status.ReadyKnowledges = 0 obj.Status.TotalKnowledges = len(obj.Spec.Knowledges) for _, knowledgeRef := range obj.Spec.Knowledges { @@ -245,8 +250,9 @@ func (c *BasePipelineController[PipelineType]) handleStepChange( meta.RemoveStatusCondition(&obj.Status.Conditions, v1alpha1.StepConditionError) log.Info("step is ready", "stepName", obj.Name) } - if err := c.Status().Update(ctx, obj); err != nil { - log.Error(err, "failed to update step status", "stepName", obj.Name) + patch := client.MergeFrom(old) + if err := c.Status().Patch(ctx, obj, patch); err != nil { + log.Error(err, "failed to patch step status", "stepName", obj.Name) return } // Find all pipelines depending on this step and re-evaluate them. diff --git a/internal/scheduling/reservations/commitments/syncer.go b/internal/scheduling/reservations/commitments/syncer.go index b09a5813..969812e9 100644 --- a/internal/scheduling/reservations/commitments/syncer.go +++ b/internal/scheduling/reservations/commitments/syncer.go @@ -236,9 +236,11 @@ func (s *Syncer) SyncReservations(ctx context.Context) error { continue } // Reservation exists, update it. + old := existing.DeepCopy() existing.Spec = res.Spec - if err := s.Update(ctx, &existing); err != nil { - syncLog.Error(err, "failed to update reservation", "name", nn.Name) + patch := client.MergeFrom(old) + if err := s.Patch(ctx, &existing, patch); err != nil { + syncLog.Error(err, "failed to patch reservation", "name", nn.Name) return err } syncLog.Info("updated reservation", "name", nn.Name) diff --git a/internal/scheduling/reservations/controller/controller.go b/internal/scheduling/reservations/controller/controller.go index 809b8f85..1affd699 100644 --- a/internal/scheduling/reservations/controller/controller.go +++ b/internal/scheduling/reservations/controller/controller.go @@ -58,6 +58,7 @@ func (r *ReservationReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Currently we can only reconcile cortex-nova reservations. if res.Spec.Scheduler.CortexNova == nil { log.Info("reservation is not a cortex-nova reservation, skipping", "reservation", req.Name) + old := res.DeepCopy() meta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReservationConditionError, Status: metav1.ConditionTrue, @@ -65,8 +66,9 @@ func (r *ReservationReconciler) Reconcile(ctx context.Context, req ctrl.Request) Message: "reservation is not a cortex-nova reservation", }) res.Status.Phase = v1alpha1.ReservationStatusPhaseFailed - if err := r.Client.Status().Update(ctx, &res); err != nil { - log.Error(err, "failed to update reservation status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, &res, patch); err != nil { + log.Error(err, "failed to patch reservation status") return ctrl.Result{}, err } return ctrl.Result{}, nil // Don't need to requeue. @@ -154,6 +156,7 @@ func (r *ReservationReconciler) Reconcile(ctx context.Context, req ctrl.Request) } if len(externalSchedulerResponse.Hosts) == 0 { log.Info("no hosts found for reservation", "reservation", req.Name) + old := res.DeepCopy() meta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReservationConditionError, Status: metav1.ConditionTrue, @@ -161,8 +164,9 @@ func (r *ReservationReconciler) Reconcile(ctx context.Context, req ctrl.Request) Message: "no hosts found for reservation", }) res.Status.Phase = v1alpha1.ReservationStatusPhaseFailed - if err := r.Status().Update(ctx, &res); err != nil { - log.Error(err, "failed to update reservation status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, &res, patch); err != nil { + log.Error(err, "failed to patch reservation status") return ctrl.Result{}, err } return ctrl.Result{}, nil // No need to requeue, we didn't find a host. @@ -170,11 +174,13 @@ func (r *ReservationReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Update the reservation with the found host (idx 0) host := externalSchedulerResponse.Hosts[0] log.Info("found host for reservation", "reservation", req.Name, "host", host) + old := res.DeepCopy() meta.RemoveStatusCondition(&res.Status.Conditions, v1alpha1.ReservationConditionError) res.Status.Phase = v1alpha1.ReservationStatusPhaseActive res.Status.Host = host - if err := r.Status().Update(ctx, &res); err != nil { - log.Error(err, "failed to update reservation status") + patch := client.MergeFrom(old) + if err := r.Status().Patch(ctx, &res, patch); err != nil { + log.Error(err, "failed to patch reservation status") return ctrl.Result{}, err } return ctrl.Result{}, nil // No need to requeue, the reservation is now active.