Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions internal/kubeadm/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ type Configuration struct {
InitConfiguration kubeadmapi.InitConfiguration
Kubeconfig clientcmdapiv1.Config
Parameters Parameters
// KubeletPatches is the raw JSON of the TCP's kubelet configurationJSONPatches.
// Included in Checksum so that changes to the patches invalidate the cached
// kubeadm-phase status and trigger a re-upload of kubelet-config.
KubeletPatches []byte
}

func (c *Configuration) Checksum() string {
Expand All @@ -26,6 +30,7 @@ func (c *Configuration) Checksum() string {
"InitConfiguration": initConfiguration,
"Kubeconfig": kubeconfig,
"Parameters": parameters,
"KubeletPatches": c.KubeletPatches,
}

return utilities.CalculateMapChecksum(data)
Expand Down
62 changes: 62 additions & 0 deletions internal/kubeadm/types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0

package kubeadm_test

import (
"testing"

"github.com/clastix/kamaji/internal/kubeadm"
)

// TestConfigurationChecksum_KubeletPatchesAffectChecksum guards against the
// regression that left worker nodes unable to join after a
// `spec.kubernetes.kubelet.configurationJSONPatches` change: if the kubelet
// patches do not contribute to the kubeadm-phase checksum, the cached
// upload-config-kubelet phase short-circuits and the tenant's kubelet-config
// ConfigMap is never re-uploaded. See clastix/kamaji#XXXX.
func TestConfigurationChecksum_KubeletPatchesAffectChecksum(t *testing.T) {
base := kubeadm.Configuration{
Parameters: kubeadm.Parameters{
TenantControlPlaneName: "tcp",
TenantControlPlaneVersion: "v1.34.0",
},
}

withPatches := base
withPatches.KubeletPatches = []byte(`[{"op":"add","path":"/cgroupDriver","value":"systemd"}]`)

withDifferentPatches := base
withDifferentPatches.KubeletPatches = []byte(`[{"op":"remove","path":"/crashLoopBackOff"}]`)

bareSum := base.Checksum()
patchedSum := withPatches.Checksum()
otherPatchedSum := withDifferentPatches.Checksum()

if bareSum == patchedSum {
t.Errorf("expected adding kubelet patches to change the checksum, got %q for both", bareSum)
}
if patchedSum == otherPatchedSum {
t.Errorf("expected different kubelet patches to produce different checksums, got %q for both", patchedSum)
}

// Idempotence: same configuration must produce the same checksum.
if got := withPatches.Checksum(); got != patchedSum {
t.Errorf("checksum is not idempotent: first=%q second=%q", patchedSum, got)
}
}

// TestConfigurationChecksum_NilAndEmptyPatchesAreEquivalent documents the
// expected behavior for the empty case: a TCP without configurationJSONPatches
// must produce the same checksum as a TCP whose patches were just removed.
// Otherwise removing all patches would also force a re-upload, which is fine
// (the kubelet-config CM should match the spec) but worth pinning explicitly.
func TestConfigurationChecksum_NilAndEmptyPatchesAreEquivalent(t *testing.T) {
a := kubeadm.Configuration{Parameters: kubeadm.Parameters{TenantControlPlaneName: "tcp"}}
b := a
b.KubeletPatches = []byte(nil)

if a.Checksum() != b.Checksum() {
t.Errorf("nil and empty KubeletPatches must hash identically, got %q vs %q", a.Checksum(), b.Checksum())
}
}
9 changes: 9 additions & 0 deletions internal/resources/kubeadm_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,15 @@ func KubeadmPhaseCreate(ctx context.Context, r KubeadmPhaseResource, logger logr
TenantControlPlaneCGroupDriver: tenantControlPlane.Spec.Kubernetes.Kubelet.CGroupFS.String(), //nolint:staticcheck
}

// Hash kubelet JSON patches into the phase checksum so changes to
// spec.kubernetes.kubelet.configurationJSONPatches actually invalidate
// the cached upload-config-kubelet phase and trigger a re-upload.
if len(tenantControlPlane.Spec.Kubernetes.Kubelet.ConfigurationJSONPatches) > 0 {
if jsonP, jpErr := tenantControlPlane.Spec.Kubernetes.Kubelet.ConfigurationJSONPatches.ToJSON(); jpErr == nil {
config.KubeletPatches = jsonP
}
}

var checksum string

status, err := r.GetStatus(tenantControlPlane)
Expand Down