Skip to content

Commit 2138274

Browse files
committed
spike: reduce MC restarts by hashing configmap data
1 parent 7fa8698 commit 2138274

2 files changed

Lines changed: 103 additions & 1 deletion

File tree

pkg/splunk/enterprise/monitoringconsole.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package enterprise
1717

1818
import (
1919
"context"
20+
"crypto/sha256"
21+
"encoding/hex"
2022
"fmt"
2123
"reflect"
2224
"sort"
@@ -199,13 +201,41 @@ func getMonitoringConsoleStatefulSet(ctx context.Context, client splcommon.Contr
199201
if err != nil {
200202
return nil, err
201203
}
202-
ss.Spec.Template.ObjectMeta.Annotations[monitoringConsoleConfigRev] = monitoringConsoleConfigMap.ResourceVersion
204+
if ss.Spec.Template.ObjectMeta.Annotations == nil {
205+
ss.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
206+
}
207+
ss.Spec.Template.ObjectMeta.Annotations[monitoringConsoleConfigRev] = getMonitoringConsoleConfigDataHash(monitoringConsoleConfigMap.Data)
203208

204209
// Setup App framework staging volume for apps
205210
setupAppsStagingVolume(ctx, client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig)
206211
return ss, nil
207212
}
208213

214+
// getMonitoringConsoleConfigDataHash returns a stable hash for MC configMap data.
215+
// Hashing data (instead of configMap resourceVersion) avoids unnecessary MC restarts
216+
// when only metadata changes.
217+
func getMonitoringConsoleConfigDataHash(data map[string]string) string {
218+
if len(data) == 0 {
219+
return ""
220+
}
221+
222+
keys := make([]string, 0, len(data))
223+
for key := range data {
224+
keys = append(keys, key)
225+
}
226+
sort.Strings(keys)
227+
228+
hash := sha256.New()
229+
for _, key := range keys {
230+
hash.Write([]byte(key))
231+
hash.Write([]byte{0})
232+
hash.Write([]byte(data[key]))
233+
hash.Write([]byte{0})
234+
}
235+
236+
return hex.EncodeToString(hash.Sum(nil))
237+
}
238+
209239
// helper function to get the list of MonitoringConsole types in the current namespace
210240
func getMonitoringConsoleList(ctx context.Context, c splcommon.ControllerClient, cr splcommon.MetaObject, listOpts []rclient.ListOption) (enterpriseApi.MonitoringConsoleList, error) {
211241
reqLogger := log.FromContext(ctx)

pkg/splunk/enterprise/monitoringconsole_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,78 @@ func TestGetMonitoringConsoleStatefulSet(t *testing.T) {
475475
cr.ObjectMeta.Labels["app.kubernetes.io/test-extra-label"] = "test-extra-label-value"
476476
test(loadFixture(t, "statefulset_stack1_monitoring_console_with_service_account_1.json"))
477477
}
478+
479+
func TestGetMonitoringConsoleConfigDataHash(t *testing.T) {
480+
if got := getMonitoringConsoleConfigDataHash(nil); got != "" {
481+
t.Errorf("Expected empty hash for nil data, got=%s", got)
482+
}
483+
484+
dataA := map[string]string{"SPLUNK_SEARCH_HEAD_URL": "sh1,sh2", "SPLUNK_CLUSTER_MANAGER_URL": "cm1"}
485+
dataB := map[string]string{"SPLUNK_CLUSTER_MANAGER_URL": "cm1", "SPLUNK_SEARCH_HEAD_URL": "sh1,sh2"}
486+
dataC := map[string]string{"SPLUNK_CLUSTER_MANAGER_URL": "cm1", "SPLUNK_SEARCH_HEAD_URL": "sh1,sh3"}
487+
488+
hashA := getMonitoringConsoleConfigDataHash(dataA)
489+
hashB := getMonitoringConsoleConfigDataHash(dataB)
490+
hashC := getMonitoringConsoleConfigDataHash(dataC)
491+
492+
if hashA == "" {
493+
t.Errorf("Expected non-empty hash for non-empty data")
494+
}
495+
if hashA != hashB {
496+
t.Errorf("Expected stable hash for same data regardless of map iteration order")
497+
}
498+
if hashA == hashC {
499+
t.Errorf("Expected different hash when data changes")
500+
}
501+
}
502+
503+
func TestGetMonitoringConsoleStatefulSetUsesConfigDataHash(t *testing.T) {
504+
os.Setenv("SPLUNK_GENERAL_TERMS", "--accept-sgt-current-at-splunk-com")
505+
ctx := context.TODO()
506+
507+
cr := enterpriseApi.MonitoringConsole{
508+
ObjectMeta: metav1.ObjectMeta{
509+
Name: "stack1",
510+
Namespace: "test",
511+
},
512+
}
513+
514+
c := spltest.NewMockClient()
515+
_, err := splutil.ApplyNamespaceScopedSecretObject(ctx, c, "test")
516+
if err != nil {
517+
t.Fatalf("Failed to create namespace scoped secret: %v", err)
518+
}
519+
520+
monitoringConsoleConfigMapName := GetSplunkMonitoringconsoleConfigMapName(cr.GetName(), SplunkMonitoringConsole)
521+
monitoringConsoleConfigMap := corev1.ConfigMap{
522+
ObjectMeta: metav1.ObjectMeta{
523+
Name: monitoringConsoleConfigMapName,
524+
Namespace: "test",
525+
ResourceVersion: "12345",
526+
},
527+
Data: map[string]string{
528+
"SPLUNK_CLUSTER_MANAGER_URL": "cm1",
529+
"SPLUNK_SEARCH_HEAD_URL": "sh1,sh2",
530+
},
531+
}
532+
c.AddObject(&monitoringConsoleConfigMap)
533+
534+
ss, err := getMonitoringConsoleStatefulSet(ctx, c, &cr)
535+
if err != nil {
536+
t.Fatalf("getMonitoringConsoleStatefulSet() returned error: %v", err)
537+
}
538+
539+
got := ss.Spec.Template.ObjectMeta.Annotations[monitoringConsoleConfigRev]
540+
want := getMonitoringConsoleConfigDataHash(monitoringConsoleConfigMap.Data)
541+
542+
if got != want {
543+
t.Errorf("Expected monitoringConsoleConfigRev to be data hash. got=%s want=%s", got, want)
544+
}
545+
if got == monitoringConsoleConfigMap.ResourceVersion {
546+
t.Errorf("monitoringConsoleConfigRev must not use configMap resourceVersion")
547+
}
548+
}
549+
478550
func TestMonitoringConsoleSpecNotCreatedWithoutGeneralTerms(t *testing.T) {
479551
// Unset the SPLUNK_GENERAL_TERMS environment variable
480552
os.Unsetenv("SPLUNK_GENERAL_TERMS")

0 commit comments

Comments
 (0)