From 2138274439a29804e3070535bc7a0591d6db911a Mon Sep 17 00:00:00 2001 From: vivekr-splunk Date: Wed, 25 Feb 2026 21:41:32 -0800 Subject: [PATCH 1/2] spike: reduce MC restarts by hashing configmap data --- pkg/splunk/enterprise/monitoringconsole.go | 32 ++++++++- .../enterprise/monitoringconsole_test.go | 72 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/pkg/splunk/enterprise/monitoringconsole.go b/pkg/splunk/enterprise/monitoringconsole.go index 0bbc54047..744cf8e51 100644 --- a/pkg/splunk/enterprise/monitoringconsole.go +++ b/pkg/splunk/enterprise/monitoringconsole.go @@ -17,6 +17,8 @@ package enterprise import ( "context" + "crypto/sha256" + "encoding/hex" "fmt" "reflect" "sort" @@ -199,13 +201,41 @@ func getMonitoringConsoleStatefulSet(ctx context.Context, client splcommon.Contr if err != nil { return nil, err } - ss.Spec.Template.ObjectMeta.Annotations[monitoringConsoleConfigRev] = monitoringConsoleConfigMap.ResourceVersion + if ss.Spec.Template.ObjectMeta.Annotations == nil { + ss.Spec.Template.ObjectMeta.Annotations = make(map[string]string) + } + ss.Spec.Template.ObjectMeta.Annotations[monitoringConsoleConfigRev] = getMonitoringConsoleConfigDataHash(monitoringConsoleConfigMap.Data) // Setup App framework staging volume for apps setupAppsStagingVolume(ctx, client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig) return ss, nil } +// getMonitoringConsoleConfigDataHash returns a stable hash for MC configMap data. +// Hashing data (instead of configMap resourceVersion) avoids unnecessary MC restarts +// when only metadata changes. +func getMonitoringConsoleConfigDataHash(data map[string]string) string { + if len(data) == 0 { + return "" + } + + keys := make([]string, 0, len(data)) + for key := range data { + keys = append(keys, key) + } + sort.Strings(keys) + + hash := sha256.New() + for _, key := range keys { + hash.Write([]byte(key)) + hash.Write([]byte{0}) + hash.Write([]byte(data[key])) + hash.Write([]byte{0}) + } + + return hex.EncodeToString(hash.Sum(nil)) +} + // helper function to get the list of MonitoringConsole types in the current namespace func getMonitoringConsoleList(ctx context.Context, c splcommon.ControllerClient, cr splcommon.MetaObject, listOpts []rclient.ListOption) (enterpriseApi.MonitoringConsoleList, error) { reqLogger := log.FromContext(ctx) diff --git a/pkg/splunk/enterprise/monitoringconsole_test.go b/pkg/splunk/enterprise/monitoringconsole_test.go index af3eac5b4..9ae48c001 100644 --- a/pkg/splunk/enterprise/monitoringconsole_test.go +++ b/pkg/splunk/enterprise/monitoringconsole_test.go @@ -475,6 +475,78 @@ func TestGetMonitoringConsoleStatefulSet(t *testing.T) { cr.ObjectMeta.Labels["app.kubernetes.io/test-extra-label"] = "test-extra-label-value" test(loadFixture(t, "statefulset_stack1_monitoring_console_with_service_account_1.json")) } + +func TestGetMonitoringConsoleConfigDataHash(t *testing.T) { + if got := getMonitoringConsoleConfigDataHash(nil); got != "" { + t.Errorf("Expected empty hash for nil data, got=%s", got) + } + + dataA := map[string]string{"SPLUNK_SEARCH_HEAD_URL": "sh1,sh2", "SPLUNK_CLUSTER_MANAGER_URL": "cm1"} + dataB := map[string]string{"SPLUNK_CLUSTER_MANAGER_URL": "cm1", "SPLUNK_SEARCH_HEAD_URL": "sh1,sh2"} + dataC := map[string]string{"SPLUNK_CLUSTER_MANAGER_URL": "cm1", "SPLUNK_SEARCH_HEAD_URL": "sh1,sh3"} + + hashA := getMonitoringConsoleConfigDataHash(dataA) + hashB := getMonitoringConsoleConfigDataHash(dataB) + hashC := getMonitoringConsoleConfigDataHash(dataC) + + if hashA == "" { + t.Errorf("Expected non-empty hash for non-empty data") + } + if hashA != hashB { + t.Errorf("Expected stable hash for same data regardless of map iteration order") + } + if hashA == hashC { + t.Errorf("Expected different hash when data changes") + } +} + +func TestGetMonitoringConsoleStatefulSetUsesConfigDataHash(t *testing.T) { + os.Setenv("SPLUNK_GENERAL_TERMS", "--accept-sgt-current-at-splunk-com") + ctx := context.TODO() + + cr := enterpriseApi.MonitoringConsole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "stack1", + Namespace: "test", + }, + } + + c := spltest.NewMockClient() + _, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test") + if err != nil { + t.Fatalf("Failed to create namespace scoped secret: %v", err) + } + + monitoringConsoleConfigMapName := GetSplunkMonitoringconsoleConfigMapName(cr.GetName(), SplunkMonitoringConsole) + monitoringConsoleConfigMap := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: monitoringConsoleConfigMapName, + Namespace: "test", + ResourceVersion: "12345", + }, + Data: map[string]string{ + "SPLUNK_CLUSTER_MANAGER_URL": "cm1", + "SPLUNK_SEARCH_HEAD_URL": "sh1,sh2", + }, + } + c.AddObject(&monitoringConsoleConfigMap) + + ss, err := getMonitoringConsoleStatefulSet(ctx, c, &cr) + if err != nil { + t.Fatalf("getMonitoringConsoleStatefulSet() returned error: %v", err) + } + + got := ss.Spec.Template.ObjectMeta.Annotations[monitoringConsoleConfigRev] + want := getMonitoringConsoleConfigDataHash(monitoringConsoleConfigMap.Data) + + if got != want { + t.Errorf("Expected monitoringConsoleConfigRev to be data hash. got=%s want=%s", got, want) + } + if got == monitoringConsoleConfigMap.ResourceVersion { + t.Errorf("monitoringConsoleConfigRev must not use configMap resourceVersion") + } +} + func TestMonitoringConsoleSpecNotCreatedWithoutGeneralTerms(t *testing.T) { // Unset the SPLUNK_GENERAL_TERMS environment variable os.Unsetenv("SPLUNK_GENERAL_TERMS") From ef55ceec2d74b13f946b98add67a1b426a89e30e Mon Sep 17 00:00:00 2001 From: vivekr-splunk Date: Wed, 25 Feb 2026 21:42:22 -0800 Subject: [PATCH 2/2] spike: align MC annotation comment with hash behavior --- pkg/splunk/enterprise/monitoringconsole.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/splunk/enterprise/monitoringconsole.go b/pkg/splunk/enterprise/monitoringconsole.go index 744cf8e51..49ded646a 100644 --- a/pkg/splunk/enterprise/monitoringconsole.go +++ b/pkg/splunk/enterprise/monitoringconsole.go @@ -195,7 +195,7 @@ func getMonitoringConsoleStatefulSet(ctx context.Context, client splcommon.Contr }, } - //update podTemplate annotation with configMap resource version + // update podTemplate annotation with hash of configMap data namespacedName := types.NamespacedName{Namespace: cr.GetNamespace(), Name: configMap} monitoringConsoleConfigMap, err = splctrl.GetMCConfigMap(ctx, client, cr, namespacedName) if err != nil {