From a205a838298841d780056d9c21c4340076214d99 Mon Sep 17 00:00:00 2001 From: Varun chandu Pakalapati Date: Sun, 14 Dec 2025 08:53:09 -0600 Subject: [PATCH] fix: prevent reconciliation loop for parameters pending reboot - Skip modifying/resetting parameters that are already pending-reboot - Prevents continuous loop when parameters are in transitional state - Applies to both DBClusterParameterGroup and DBParameterGroup - Fixes issue where ACK continuously tries to modify parameters after reboot The loop occurred because: 1. Parameter set to pending-reboot status 2. After reboot, parameter at default but status still shows pending-reboot 3. ACK sees mismatch and tries to modify/reset again 4. Loop continues indefinitely Now ACK checks ParameterOverrideStatuses before modifying/resetting parameters and skips any that are already pending-reboot. --- .../db_cluster_parameter_group/hooks.go | 53 +++++++++++++++++++ pkg/resource/db_parameter_group/hooks.go | 53 +++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/pkg/resource/db_cluster_parameter_group/hooks.go b/pkg/resource/db_cluster_parameter_group/hooks.go index a3478bc8..01df363c 100644 --- a/pkg/resource/db_cluster_parameter_group/hooks.go +++ b/pkg/resource/db_cluster_parameter_group/hooks.go @@ -242,6 +242,59 @@ func (rm *resourceManager) syncParameters( desiredOverrides, latestOverrides, ) + // Filter out parameters that are already pending reboot from both toModify and toDelete. + // When a parameter is reset or modified, it remains as a user override with pending-reboot + // status until the DB cluster is rebooted. Attempting to modify or reset it again + // would cause a reconciliation loop. We skip modifying/resetting parameters that are + // already pending reboot until after the reboot completes. + if latest != nil && latest.ko.Status.ParameterOverrideStatuses != nil { + pendingRebootParams := make(map[string]bool) + + // Build a map of parameters that are pending reboot + for _, status := range latest.ko.Status.ParameterOverrideStatuses { + if status.ParameterName != nil && status.ApplyMethod != nil { + if *status.ApplyMethod == "pending-reboot" { + pendingRebootParams[*status.ParameterName] = true + } + } + } + + // Filter toDelete: skip parameters that are pending reboot + if len(toDelete) > 0 { + filteredToDelete := util.Parameters{} + for paramName, paramValue := range toDelete { + if !pendingRebootParams[paramName] { + filteredToDelete[paramName] = paramValue + } else { + rlog.Debug( + "skipping reset of parameter already pending reboot", + "parameter", paramName, + ) + } + } + toDelete = filteredToDelete + } + + // Filter toModify: skip parameters that are pending reboot + // This prevents trying to modify parameters that are in a transitional state + // after reboot but before the status is updated + if len(toModify) > 0 { + filteredToModify := util.Parameters{} + for paramName, paramValue := range toModify { + if !pendingRebootParams[paramName] { + filteredToModify[paramName] = paramValue + } else { + rlog.Debug( + "skipping modify of parameter already pending reboot", + "parameter", paramName, + "desired_value", paramValue, + ) + } + } + toModify = filteredToModify + } + } + if len(toDelete) > 0 { err = rm.resetParameters(ctx, family, groupName, toDelete) if err != nil { diff --git a/pkg/resource/db_parameter_group/hooks.go b/pkg/resource/db_parameter_group/hooks.go index 41b41d80..c496bbd8 100644 --- a/pkg/resource/db_parameter_group/hooks.go +++ b/pkg/resource/db_parameter_group/hooks.go @@ -230,6 +230,59 @@ func (rm *resourceManager) syncParameters( desiredOverrides, latestOverrides, ) + // Filter out parameters that are already pending reboot from both toModify and toDelete. + // When a parameter is reset or modified, it remains as a user override with pending-reboot + // status until the DB instance is rebooted. Attempting to modify or reset it again + // would cause a reconciliation loop. We skip modifying/resetting parameters that are + // already pending reboot until after the reboot completes. + if latest != nil && latest.ko.Status.ParameterOverrideStatuses != nil { + pendingRebootParams := make(map[string]bool) + + // Build a map of parameters that are pending reboot + for _, status := range latest.ko.Status.ParameterOverrideStatuses { + if status.ParameterName != nil && status.ApplyMethod != nil { + if *status.ApplyMethod == "pending-reboot" { + pendingRebootParams[*status.ParameterName] = true + } + } + } + + // Filter toDelete: skip parameters that are pending reboot + if len(toDelete) > 0 { + filteredToDelete := util.Parameters{} + for paramName, paramValue := range toDelete { + if !pendingRebootParams[paramName] { + filteredToDelete[paramName] = paramValue + } else { + rlog.Debug( + "skipping reset of parameter already pending reboot", + "parameter", paramName, + ) + } + } + toDelete = filteredToDelete + } + + // Filter toModify: skip parameters that are pending reboot + // This prevents trying to modify parameters that are in a transitional state + // after reboot but before the status is updated + if len(toModify) > 0 { + filteredToModify := util.Parameters{} + for paramName, paramValue := range toModify { + if !pendingRebootParams[paramName] { + filteredToModify[paramName] = paramValue + } else { + rlog.Debug( + "skipping modify of parameter already pending reboot", + "parameter", paramName, + "desired_value", paramValue, + ) + } + } + toModify = filteredToModify + } + } + // NOTE(jaypipes): ResetDBParameterGroup and ModifyDBParameterGroup only // accept 20 parameters at a time, which is why we "chunk" both the deleted // and modified parameter sets.