Skip to content

Commit 34b3d3f

Browse files
authored
Add functionality to wait for Postgres Cluster image updates before applying configuration setting (#2534)
* Add functionality to wait for Postgres Cluster image updates before applying configuration setting Signed-off-by: Daniel Fan <[email protected]> * add permissions for PostgreSQL clusters Signed-off-by: Daniel Fan <[email protected]> * Add check for Postgres Cluster CRD existence before image update Signed-off-by: Daniel Fan <[email protected]> --------- Signed-off-by: Daniel Fan <[email protected]>
1 parent f219702 commit 34b3d3f

File tree

7 files changed

+232
-2
lines changed

7 files changed

+232
-2
lines changed

bundle/manifests/ibm-common-service-operator.clusterserviceversion.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ metadata:
2323
capabilities: Seamless Upgrades
2424
cloudPakThemesVersion: styles4100.css
2525
containerImage: icr.io/cpopen/common-service-operator:4.13.0
26-
createdAt: "2025-05-13T13:53:23Z"
26+
createdAt: "2025-05-16T03:15:01Z"
2727
description: The IBM Cloud Pak foundational services operator is used to deploy IBM foundational services.
2828
features.operators.openshift.io/disconnected: "true"
2929
features.operators.openshift.io/fips-compliant: "true"
@@ -610,6 +610,13 @@ spec:
610610
- patch
611611
- update
612612
- watch
613+
- apiGroups:
614+
- postgresql.k8s.enterprisedb.io
615+
resources:
616+
- clusters
617+
verbs:
618+
- get
619+
- list
613620
serviceAccountName: ibm-common-service-operator
614621
strategy: deployment
615622
installModes:

config/rbac/role.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,10 @@ rules:
273273
- patch
274274
- update
275275
- watch
276+
- apiGroups:
277+
- postgresql.k8s.enterprisedb.io
278+
resources:
279+
- clusters
280+
verbs:
281+
- get
282+
- list

helm/templates/rbac.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,5 +235,12 @@ rules:
235235
- patch
236236
- update
237237
- watch
238+
- apiGroups:
239+
- postgresql.k8s.enterprisedb.io
240+
resources:
241+
- clusters
242+
verbs:
243+
- get
244+
- list
238245
---
239246
{{- end }}

internal/controller/bootstrap/init.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2515,3 +2515,175 @@ func (b *Bootstrap) fetchSubscription(subName, packageManifest, operatorNs strin
25152515
}
25162516
return sub, nil
25172517
}
2518+
2519+
// Wait for Postgres Cluster CR image to be updated with the image from the configmap
2520+
func (b *Bootstrap) WaitForPostgresClusterImageUpdate(ctx context.Context, instance *apiv3.CommonService) error {
2521+
// Check if Postgres Cluster CR "common-service-db" is created in service namespace
2522+
pgCluster := &unstructured.Unstructured{}
2523+
pgCluster.SetGroupVersionKind(schema.GroupVersionKind{
2524+
Group: constant.PGClusterGroup,
2525+
Version: "v1",
2526+
Kind: constant.PGClusterKind,
2527+
})
2528+
2529+
if clusterCRDExists, err := b.CheckCRD(constant.PGClusterGroup+"/v1", constant.PGClusterKind); err != nil {
2530+
klog.Errorf("Failed to check if Postgres Cluster CRD exists: %v", err)
2531+
return err
2532+
} else if !clusterCRDExists {
2533+
klog.Infof("Postgres %s Cluster CRD not found, skipping Postgres Cluster image update check", constant.PGClusterGroup+"/v1")
2534+
return nil
2535+
}
2536+
2537+
if err := b.Client.Get(ctx, types.NamespacedName{
2538+
Name: constant.CSPGCluster,
2539+
Namespace: b.CSData.ServicesNs,
2540+
}, pgCluster); err != nil {
2541+
if errors.IsNotFound(err) {
2542+
klog.Infof("Postgres Cluster CR %s not found in namespace %s, skipping Cluster CR image update check", constant.CSPGCluster, b.CSData.ServicesNs)
2543+
return nil
2544+
}
2545+
return err
2546+
}
2547+
2548+
configMap, err := b.getPostgresImageConfigMap(ctx)
2549+
if err != nil {
2550+
return err
2551+
} else if configMap == nil {
2552+
klog.Infof("Neither %s nor %s configmap found in namespace %s, skipping Postgres Cluster image update check",
2553+
constant.PostgreSQLImageConfigMap, constant.CSPostgreSQLImageConfigMap, b.CSData.OperatorNs)
2554+
return nil
2555+
}
2556+
configMapName := configMap.GetName()
2557+
imageKeyName := constant.PostgreSQL16ImageKey
2558+
2559+
// Get the image from the configmap
2560+
desiredImage, exists := configMap.Data[imageKeyName]
2561+
if !exists {
2562+
klog.Infof("Image key %s not found in configmap %s/%s, skipping image update check",
2563+
imageKeyName, b.CSData.OperatorNs, configMapName)
2564+
return nil
2565+
}
2566+
2567+
// Get current image from the Postgres Cluster CR
2568+
currentImage, found, err := unstructured.NestedString(pgCluster.Object, "spec", "imageName")
2569+
if err != nil {
2570+
return err
2571+
}
2572+
2573+
// If image is already updated, return nil
2574+
if found && currentImage == desiredImage {
2575+
klog.Infof("Postgres Cluster CR %s image already updated to the desired image in configmap %s/%s",
2576+
constant.CSPGCluster, b.CSData.OperatorNs, configMapName)
2577+
return nil
2578+
}
2579+
2580+
// Update the configmap with ODLM metadata to trigger ODLM reconciliation
2581+
if err := b.updateConfigMapWithODLMMetadata(ctx, configMap); err != nil {
2582+
return err
2583+
}
2584+
2585+
// Wait for the image to be updated
2586+
return utilwait.PollImmediate(time.Second*10, time.Minute*2, func() (done bool, err error) {
2587+
// Fetch the latest Postgres Cluster CR
2588+
err = b.Client.Get(ctx, types.NamespacedName{
2589+
Name: constant.CSPGCluster,
2590+
Namespace: b.CSData.ServicesNs,
2591+
}, pgCluster)
2592+
2593+
if err != nil {
2594+
if errors.IsNotFound(err) {
2595+
return true, nil
2596+
}
2597+
return false, err
2598+
}
2599+
2600+
// Check if image is updated
2601+
currentImage, found, err := unstructured.NestedString(pgCluster.Object, "spec", "imageName")
2602+
if err != nil {
2603+
return false, err
2604+
}
2605+
2606+
if found && currentImage == desiredImage {
2607+
klog.Infof("Postgres Cluster CR %s image updated to the desired image in configmap %s/%s",
2608+
constant.CSPGCluster, b.CSData.OperatorNs, configMapName)
2609+
return true, nil
2610+
}
2611+
2612+
klog.Infof("Postgres Cluster CR %s image is not updated, waiting for update to the desired image in configmap %s/%s",
2613+
constant.CSPGCluster, b.CSData.OperatorNs, configMapName)
2614+
return false, nil
2615+
})
2616+
}
2617+
2618+
// getPostgresImageConfigMap gets the configmap containing PostgreSQL image information
2619+
// It first tries to get the configmap deployed with Postgres Operator,
2620+
// and if not found, it looks for the configmap created by CS operator
2621+
func (b *Bootstrap) getPostgresImageConfigMap(ctx context.Context) (*corev1.ConfigMap, error) {
2622+
configMap := &corev1.ConfigMap{}
2623+
configMapName := constant.PostgreSQLImageConfigMap
2624+
2625+
if err := b.Client.Get(ctx, types.NamespacedName{
2626+
Name: configMapName,
2627+
Namespace: b.CSData.OperatorNs,
2628+
}, configMap); err != nil && errors.IsNotFound(err) {
2629+
// If the configmap is not found, find configmap created by CS operator
2630+
configMapName = constant.CSPostgreSQLImageConfigMap
2631+
2632+
if err := b.Client.Get(ctx, types.NamespacedName{
2633+
Name: configMapName,
2634+
Namespace: b.CSData.OperatorNs,
2635+
}, configMap); err != nil {
2636+
if errors.IsNotFound(err) {
2637+
2638+
return nil, nil
2639+
}
2640+
klog.Errorf("Failed to get configmap %s in namespace %s: %v", configMapName, b.CSData.OperatorNs, err)
2641+
return nil, err
2642+
}
2643+
} else if err != nil {
2644+
klog.Errorf("Failed to get configmap %s in namespace %s: %v", configMapName, b.CSData.OperatorNs, err)
2645+
return nil, err
2646+
}
2647+
2648+
return configMap, nil
2649+
}
2650+
2651+
// updateConfigMapWithODLMMetadata adds ODLM-specific labels and annotations to a ConfigMap
2652+
// to ensure it's properly reconciled by the Operand Deployment Lifecycle Manager
2653+
func (b *Bootstrap) updateConfigMapWithODLMMetadata(ctx context.Context, configMap *corev1.ConfigMap) error {
2654+
// Check if the configmap has the required label and annotation
2655+
needsLabelUpdate := false
2656+
cmLabels := configMap.GetLabels()
2657+
if cmLabels == nil {
2658+
cmLabels = make(map[string]string)
2659+
needsLabelUpdate = true
2660+
}
2661+
2662+
if _, exists := cmLabels[constant.ODLMWatchLabel]; !exists {
2663+
cmLabels[constant.ODLMWatchLabel] = "true"
2664+
needsLabelUpdate = true
2665+
}
2666+
2667+
cmAnnotations := configMap.GetAnnotations()
2668+
if cmAnnotations == nil {
2669+
cmAnnotations = make(map[string]string)
2670+
needsLabelUpdate = true
2671+
}
2672+
2673+
expectedAnnotation := fmt.Sprintf("OperandConfig.%s.common-service", b.CSData.ServicesNs)
2674+
if cmAnnotations[constant.ODLMReferenceAnno] != expectedAnnotation {
2675+
cmAnnotations[constant.ODLMReferenceAnno] = expectedAnnotation
2676+
needsLabelUpdate = true
2677+
}
2678+
2679+
if needsLabelUpdate {
2680+
klog.Infof("Updating configmap %s/%s with ODLM labels and annotations to trigger the ODLM reconciliation", configMap.Namespace, configMap.Name)
2681+
configMap.SetLabels(cmLabels)
2682+
configMap.SetAnnotations(cmAnnotations)
2683+
if err := b.Client.Update(ctx, configMap); err != nil {
2684+
return err
2685+
}
2686+
}
2687+
2688+
return nil
2689+
}

internal/controller/commonservice_controller.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,23 @@ func (r *CommonServiceReconciler) ReconcileMasterCR(ctx context.Context, instanc
250250
return ctrl.Result{}, statusErr
251251
}
252252

253+
// Wait for Postgres Cluster image to be updated
254+
if err := r.Bootstrap.WaitForPostgresClusterImageUpdate(ctx, instance); err != nil {
255+
klog.Errorf("Failed to update Postgres Cluster image: %v", err)
256+
if err := r.updatePhase(ctx, instance, apiv3.CRFailed); err != nil {
257+
klog.Error(err)
258+
}
259+
klog.Errorf("Fail to reconcile %s/%s: %v", instance.Namespace, instance.Name, err)
260+
return ctrl.Result{}, err
261+
}
262+
253263
// Apply new configs to CommonService CR
254264
cs := util.NewUnstructured("operator.ibm.com", "CommonService", "v3")
255265
if statusErr = r.Bootstrap.Client.Get(ctx, types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, cs); statusErr != nil {
256266
klog.Errorf("Fail to reconcile %s/%s: %v", instance.Namespace, instance.Name, statusErr)
257267
return ctrl.Result{}, statusErr
258268
}
259-
// Set "Pengding" condition and "Updating" for phase when config CS CR
269+
// Set "Pending" condition and "Updating" for phase when config CS CR
260270
instance.SetPendingCondition(constant.MasterCR, apiv3.ConditionTypeReconciling, corev1.ConditionTrue, apiv3.ConditionReasonConfig, apiv3.ConditionMessageConfig)
261271
instance.Status.Phase = apiv3.CRUpdating
262272
newConfigs, serviceControllerMapping, statusErr := r.getNewConfigs(cs)
@@ -386,6 +396,16 @@ func (r *CommonServiceReconciler) ReconcileGeneralCR(ctx context.Context, instan
386396
return ctrl.Result{}, err
387397
}
388398

399+
// Wait for Postgres Cluster image to be updated
400+
if err := r.Bootstrap.WaitForPostgresClusterImageUpdate(ctx, instance); err != nil {
401+
klog.Errorf("Failed to update Postgres Cluster image: %v", err)
402+
if err := r.updatePhase(ctx, instance, apiv3.CRFailed); err != nil {
403+
klog.Error(err)
404+
}
405+
klog.Errorf("Fail to reconcile %s/%s: %v", instance.Namespace, instance.Name, err)
406+
return ctrl.Result{}, err
407+
}
408+
389409
newConfigs, serviceControllerMapping, err := r.getNewConfigs(cs)
390410
if err != nil {
391411
if err := r.updatePhase(ctx, instance, apiv3.CRFailed); err != nil {

internal/controller/constant/cloudNativePostgresql.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ metadata:
2424
namespace: "{{ .CPFSNs }}"
2525
labels:
2626
operator.ibm.com/managedByCsOperator: "true"
27+
operator.ibm.com/watched-by-odlm: "true"
2728
annotations:
2829
version: {{ .Version }}
2930
data:

internal/controller/constant/constant.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,22 @@ const (
122122
RequeueDuration = 30 * time.Second
123123
// OpreqLabel is the label used to label the Subscription/CR/Configmap managed by ODLM
124124
OpreqLabel string = "operator.ibm.com/opreq-control"
125+
// CSPGCluster is the name of the common service postgresql cluster
126+
CSPGCluster = "common-service-db"
127+
// PGClusterGroup is the name of the common service postgresql cluster group
128+
PGClusterGroup = "postgresql.k8s.enterprisedb.io"
129+
// PGClusterKind is the kind of the common service postgresql cluster
130+
PGClusterKind = "Cluster"
131+
// PostgreSQLImageConfigMap is the name of the postgresql image list ConfigMap deployed with Postgres Operator
132+
PostgreSQLImageConfigMap = "cloud-native-postgresql-operand-images-config"
133+
// CSPostgreSQLImageConfigMap is the name of the postgresql image list ConfigMap deployed by Common Service Operator
134+
CSPostgreSQLImageConfigMap = "cloud-native-postgresql-image-list"
135+
// PostgreSQL16ImageKey is the key for PostgreSQL 16 image in the ConfigMap
136+
PostgreSQL16ImageKey = "ibm-postgresql-16-operand-image"
137+
// ODLMWatchLabel is the label used to label the Subscription/CR/Configmap managed by ODLM
138+
ODLMWatchLabel = "operator.ibm.com/watched-by-odlm"
139+
// ODLMReferenceAnno is the annotation used to label the Subscription/CR/Configmap managed by ODLM
140+
ODLMReferenceAnno = "operator.ibm.com/referenced-by-odlm-resource"
125141
)
126142

127143
// DefaultChannels defines the default channels available for each operator

0 commit comments

Comments
 (0)