diff --git a/hypershift-operator/controllers/nodepool/nto.go b/hypershift-operator/controllers/nodepool/nto.go index 443a620268de..9bbe1022e754 100644 --- a/hypershift-operator/controllers/nodepool/nto.go +++ b/hypershift-operator/controllers/nodepool/nto.go @@ -114,6 +114,10 @@ func (r *NodePoolReconciler) reconcileMirroredConfigs(ctx context.Context, logr Name: supportutil.ShortenName(mirroredConfig.Name, nodePool.Name, validation.LabelValueMaxLength), Namespace: controlPlaneNamespace}, } + if err := r.deleteImmutableConfigMapIfNeeded(ctx, logr, cm, nodePool.Name); err != nil { + return err + } + cm.SetResourceVersion("") if result, err := r.CreateOrUpdate(ctx, r.Client, cm, func() error { return mutateMirroredConfig(cm, mirroredConfig, nodePool) }); err != nil { @@ -191,7 +195,7 @@ func reconcilePerformanceProfileConfigMap(performanceProfileConfigMap *corev1.Co } func mutateMirroredConfig(cm *corev1.ConfigMap, mirroredConfig *MirrorConfig, nodePool *hyperv1.NodePool) error { - cm.Immutable = ptr.To(true) + cm.Immutable = ptr.To(false) if cm.Annotations == nil { cm.Annotations = make(map[string]string) } @@ -205,6 +209,21 @@ func mutateMirroredConfig(cm *corev1.ConfigMap, mirroredConfig *MirrorConfig, no return nil } +func (r *NodePoolReconciler) deleteImmutableConfigMapIfNeeded(ctx context.Context, log logr.Logger, cm *corev1.ConfigMap, nodePoolName string) error { + _, err := k8sutil.DeleteIfNeededWithPredicate(ctx, r.Client, cm, func(existing *corev1.ConfigMap) bool { + if existing.Labels[NTOMirroredConfigLabel] != "true" || existing.Labels[hyperv1.NodePoolLabel] != nodePoolName { + return false + } + if existing.Immutable != nil && *existing.Immutable { + log.Info("deleting immutable mirrored ConfigMap to recreate as mutable", + "configMap", client.ObjectKeyFromObject(existing).String()) + return true + } + return false + }) + return err +} + func (r *NodePoolReconciler) getTuningConfig(ctx context.Context, nodePool *hyperv1.NodePool, ) (string, string, string, error) { diff --git a/hypershift-operator/controllers/nodepool/nto_test.go b/hypershift-operator/controllers/nodepool/nto_test.go index 16cc409fa5e5..6e1911d936a9 100644 --- a/hypershift-operator/controllers/nodepool/nto_test.go +++ b/hypershift-operator/controllers/nodepool/nto_test.go @@ -553,7 +553,7 @@ func TestReconcileMirroredConfigs(t *testing.T) { existingConfigsInHcpNs: nil, expectedMirroredConfigs: []corev1.ConfigMap{ { - Immutable: ptr.To(true), + Immutable: ptr.To(false), ObjectMeta: metav1.ObjectMeta{ Name: supportutil.ShortenName("foo", npName, validation.LabelValueMaxLength), Namespace: hcpNamespace, @@ -602,7 +602,7 @@ func TestReconcileMirroredConfigs(t *testing.T) { }, expectedMirroredConfigs: []corev1.ConfigMap{ { - Immutable: ptr.To(true), + Immutable: ptr.To(false), ObjectMeta: metav1.ObjectMeta{ Name: supportutil.ShortenName("foo", npName, validation.LabelValueMaxLength), Namespace: hcpNamespace, @@ -652,6 +652,44 @@ func TestReconcileMirroredConfigs(t *testing.T) { existingConfigsInHcpNs: nil, expectedMirroredConfigs: []corev1.ConfigMap{ { + Immutable: ptr.To(false), + ObjectMeta: metav1.ObjectMeta{ + Name: netutil.ShortenName("bar", npName, validation.LabelValueMaxLength), + Namespace: hcpNamespace, + Labels: map[string]string{ + NTOMirroredConfigLabel: "true", + nodePoolAnnotation: npName, + KubeletConfigConfigMapLabel: "true", + }, + }, + Data: map[string]string{ + TokenSecretConfigKey: kubeletConfig1, + }, + }, + }, + }, + { + name: "When an immutable mirrored ConfigMap exists and data changes, it should delete and recreate as mutable", + nodePool: np, + controlPlaneNamespace: hcpNamespace, + configsToBeMirrored: []*MirrorConfig{ + { + ConfigMap: &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: npNamespace, + }, + Data: map[string]string{ + TokenSecretConfigKey: kubeletConfig1, + }, + }, + Labels: map[string]string{ + KubeletConfigConfigMapLabel: "true", + }, + }, + }, + existingConfigsInHcpNs: []client.Object{ + &corev1.ConfigMap{ Immutable: ptr.To(true), ObjectMeta: metav1.ObjectMeta{ Name: supportutil.ShortenName("bar", npName, validation.LabelValueMaxLength), @@ -662,6 +700,133 @@ func TestReconcileMirroredConfigs(t *testing.T) { KubeletConfigConfigMapLabel: "true", }, }, + Data: map[string]string{ + TokenSecretConfigKey: "old-data", + }, + }, + }, + expectedMirroredConfigs: []corev1.ConfigMap{ + { + Immutable: ptr.To(false), + ObjectMeta: metav1.ObjectMeta{ + Name: netutil.ShortenName("bar", npName, validation.LabelValueMaxLength), + Namespace: hcpNamespace, + Labels: map[string]string{ + NTOMirroredConfigLabel: "true", + nodePoolAnnotation: npName, + KubeletConfigConfigMapLabel: "true", + }, + }, + Data: map[string]string{ + TokenSecretConfigKey: kubeletConfig1, + }, + }, + }, + }, + { + name: "When an immutable mirrored ConfigMap belongs to a different NodePool, it should not be deleted", + nodePool: np, + controlPlaneNamespace: hcpNamespace, + configsToBeMirrored: []*MirrorConfig{ + { + ConfigMap: &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: npNamespace, + }, + Data: map[string]string{ + TokenSecretConfigKey: kubeletConfig1, + }, + }, + Labels: map[string]string{ + KubeletConfigConfigMapLabel: "true", + }, + }, + }, + existingConfigsInHcpNs: []client.Object{ + &corev1.ConfigMap{ + Immutable: ptr.To(true), + ObjectMeta: metav1.ObjectMeta{ + Name: netutil.ShortenName("bar", npName, validation.LabelValueMaxLength), + Namespace: hcpNamespace, + Labels: map[string]string{ + NTOMirroredConfigLabel: "true", + nodePoolAnnotation: "other-nodepool", + KubeletConfigConfigMapLabel: "true", + }, + }, + Data: map[string]string{ + TokenSecretConfigKey: "old-data", + }, + }, + }, + expectedMirroredConfigs: []corev1.ConfigMap{ + { + Immutable: ptr.To(false), + ObjectMeta: metav1.ObjectMeta{ + Name: netutil.ShortenName("bar", npName, validation.LabelValueMaxLength), + Namespace: hcpNamespace, + Labels: map[string]string{ + NTOMirroredConfigLabel: "true", + nodePoolAnnotation: npName, + KubeletConfigConfigMapLabel: "true", + }, + }, + Data: map[string]string{ + TokenSecretConfigKey: kubeletConfig1, + }, + }, + }, + }, + { + name: "When an existing mirrored ConfigMap is already mutable, it should not be deleted", + nodePool: np, + controlPlaneNamespace: hcpNamespace, + configsToBeMirrored: []*MirrorConfig{ + { + ConfigMap: &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: npNamespace, + }, + Data: map[string]string{ + TokenSecretConfigKey: kubeletConfig1, + }, + }, + Labels: map[string]string{ + KubeletConfigConfigMapLabel: "true", + }, + }, + }, + existingConfigsInHcpNs: []client.Object{ + &corev1.ConfigMap{ + Immutable: ptr.To(false), + ObjectMeta: metav1.ObjectMeta{ + Name: netutil.ShortenName("bar", npName, validation.LabelValueMaxLength), + Namespace: hcpNamespace, + Labels: map[string]string{ + NTOMirroredConfigLabel: "true", + nodePoolAnnotation: npName, + KubeletConfigConfigMapLabel: "true", + }, + }, + Data: map[string]string{ + TokenSecretConfigKey: "old-data", + }, + }, + }, + expectedMirroredConfigs: []corev1.ConfigMap{ + { + Immutable: ptr.To(false), + ObjectMeta: metav1.ObjectMeta{ + Name: netutil.ShortenName("bar", npName, validation.LabelValueMaxLength), + Namespace: hcpNamespace, + Labels: map[string]string{ + NTOMirroredConfigLabel: "true", + nodePoolAnnotation: npName, + KubeletConfigConfigMapLabel: "true", + }, + }, Data: map[string]string{ TokenSecretConfigKey: kubeletConfig1, },