From df33f3ef495813f393a1cc9fc18555118463b9a1 Mon Sep 17 00:00:00 2001
From: Alan Bishop <abishop@redhat.com>
Date: Fri, 1 Mar 2024 08:33:31 -0800
Subject: [PATCH] Restart services when their NodeSelector changes

Add a hash of a service's Spec.NodeSelector to the inputs that
determine when the corresponding pod should be recreated. This
ensures pods are restarted whenever the NodeSelector is modified.
---
 controllers/cinderapi_controller.go       | 14 ++++++++++++++
 controllers/cinderbackup_controller.go    | 14 ++++++++++++++
 controllers/cinderscheduler_controller.go | 14 ++++++++++++++
 controllers/cindervolume_controller.go    | 14 ++++++++++++++
 pkg/cinder/funcs.go                       | 11 +++++++++++
 5 files changed, 67 insertions(+)

diff --git a/controllers/cinderapi_controller.go b/controllers/cinderapi_controller.go
index 5f6688be..9dfa712d 100644
--- a/controllers/cinderapi_controller.go
+++ b/controllers/cinderapi_controller.go
@@ -706,6 +706,20 @@ func (r *CinderAPIReconciler) reconcileNormal(ctx context.Context, instance *cin
 	// all cert input checks out so report InputReady
 	instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage)
 
+	//
+	// Hash the nodeSelector so the pod is recreated when it changes
+	//
+	err = cinder.AddNodeSelectorHash(instance.Spec.NodeSelector, &configVars)
+	if err != nil {
+		instance.Status.Conditions.Set(condition.FalseCondition(
+			condition.ServiceConfigReadyCondition,
+			condition.ErrorReason,
+			condition.SeverityWarning,
+			condition.ServiceConfigReadyErrorMessage,
+			err.Error()))
+		return ctrl.Result{}, err
+	}
+
 	//
 	// Create secrets required as input for the Service and calculate an overall hash of hashes
 	//
diff --git a/controllers/cinderbackup_controller.go b/controllers/cinderbackup_controller.go
index 0c31c956..505e1ded 100644
--- a/controllers/cinderbackup_controller.go
+++ b/controllers/cinderbackup_controller.go
@@ -410,6 +410,20 @@ func (r *CinderBackupReconciler) reconcileNormal(ctx context.Context, instance *
 	// all cert input checks out so report InputReady
 	instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage)
 
+	//
+	// Hash the nodeSelector so the pod is recreated when it changes
+	//
+	err = cinder.AddNodeSelectorHash(instance.Spec.NodeSelector, &configVars)
+	if err != nil {
+		instance.Status.Conditions.Set(condition.FalseCondition(
+			condition.ServiceConfigReadyCondition,
+			condition.ErrorReason,
+			condition.SeverityWarning,
+			condition.ServiceConfigReadyErrorMessage,
+			err.Error()))
+		return ctrl.Result{}, err
+	}
+
 	//
 	// Create secrets required as input for the Service and calculate an overall hash of hashes
 	//
diff --git a/controllers/cinderscheduler_controller.go b/controllers/cinderscheduler_controller.go
index ef158015..072ee50d 100644
--- a/controllers/cinderscheduler_controller.go
+++ b/controllers/cinderscheduler_controller.go
@@ -409,6 +409,20 @@ func (r *CinderSchedulerReconciler) reconcileNormal(ctx context.Context, instanc
 	// all cert input checks out so report InputReady
 	instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage)
 
+	//
+	// Hash the nodeSelector so the pod is recreated when it changes
+	//
+	err = cinder.AddNodeSelectorHash(instance.Spec.NodeSelector, &configVars)
+	if err != nil {
+		instance.Status.Conditions.Set(condition.FalseCondition(
+			condition.ServiceConfigReadyCondition,
+			condition.ErrorReason,
+			condition.SeverityWarning,
+			condition.ServiceConfigReadyErrorMessage,
+			err.Error()))
+		return ctrl.Result{}, err
+	}
+
 	//
 	// Create ConfigMaps required as input for the Service and calculate an overall hash of hashes
 	//
diff --git a/controllers/cindervolume_controller.go b/controllers/cindervolume_controller.go
index b784193c..553c18e0 100644
--- a/controllers/cindervolume_controller.go
+++ b/controllers/cindervolume_controller.go
@@ -434,6 +434,20 @@ func (r *CinderVolumeReconciler) reconcileNormal(ctx context.Context, instance *
 		return ctrl.Result{}, err
 	}
 
+	//
+	// Hash the nodeSelector so the pod is recreated when it changes
+	//
+	err = cinder.AddNodeSelectorHash(instance.Spec.NodeSelector, &configVars)
+	if err != nil {
+		instance.Status.Conditions.Set(condition.FalseCondition(
+			condition.ServiceConfigReadyCondition,
+			condition.ErrorReason,
+			condition.SeverityWarning,
+			condition.ServiceConfigReadyErrorMessage,
+			err.Error()))
+		return ctrl.Result{}, err
+	}
+
 	//
 	// create hash over all the different input resources to identify if any those changed
 	// and a restart/recreate is required.
diff --git a/pkg/cinder/funcs.go b/pkg/cinder/funcs.go
index 4e86c067..6b7e39e5 100644
--- a/pkg/cinder/funcs.go
+++ b/pkg/cinder/funcs.go
@@ -3,6 +3,8 @@ package cinder
 import (
 	common "github.com/openstack-k8s-operators/lib-common/modules/common"
 	"github.com/openstack-k8s-operators/lib-common/modules/common/affinity"
+	"github.com/openstack-k8s-operators/lib-common/modules/common/env"
+	"github.com/openstack-k8s-operators/lib-common/modules/common/util"
 
 	corev1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -47,3 +49,12 @@ func GetPodAffinity(componentName string) *corev1.Affinity {
 		corev1.LabelHostname,
 	)
 }
+
+// AddNodeSelectorHash - Adds a hash of a nodeSelector map to the envVars.
+func AddNodeSelectorHash(nodeSelector map[string]string, envVars *map[string]env.Setter) error {
+	hash, err := util.ObjectHash(nodeSelector)
+	if err != nil {
+		(*envVars)["NodeSelectorHash"] = env.SetValue(hash)
+	}
+	return err
+}