sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/workload_cluster_coredns_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package internal
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/blang/semver/v4"
    23  	"github.com/google/go-cmp/cmp"
    24  	. "github.com/onsi/gomega"
    25  	"github.com/pkg/errors"
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	corev1 "k8s.io/api/core/v1"
    28  	rbacv1 "k8s.io/api/rbac/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    32  
    33  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    34  	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
    35  	utilyaml "sigs.k8s.io/cluster-api/util/yaml"
    36  )
    37  
    38  func TestUpdateCoreDNS(t *testing.T) {
    39  	validKCP := &controlplanev1.KubeadmControlPlane{
    40  		Spec: controlplanev1.KubeadmControlPlaneSpec{
    41  			KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
    42  				ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
    43  					DNS: bootstrapv1.DNS{
    44  						ImageMeta: bootstrapv1.ImageMeta{
    45  							ImageRepository: "",
    46  							ImageTag:        "",
    47  						},
    48  					},
    49  					ImageRepository: "",
    50  				},
    51  			},
    52  		},
    53  	}
    54  	// This is used to force an error to be returned so we can assert the
    55  	// following pre-checks that need to happen before we retrieve the
    56  	// CoreDNSInfo.
    57  	badCM := &corev1.ConfigMap{
    58  		ObjectMeta: metav1.ObjectMeta{
    59  			Name:      coreDNSKey,
    60  			Namespace: metav1.NamespaceSystem,
    61  		},
    62  		Data: map[string]string{
    63  			"BadCoreFileKey": "",
    64  		},
    65  	}
    66  
    67  	depl := &appsv1.Deployment{
    68  		TypeMeta: metav1.TypeMeta{
    69  			Kind:       "Deployment",
    70  			APIVersion: "apps/v1",
    71  		},
    72  		ObjectMeta: metav1.ObjectMeta{
    73  			Name:      coreDNSKey,
    74  			Namespace: metav1.NamespaceSystem,
    75  		},
    76  		Spec: appsv1.DeploymentSpec{
    77  			Template: corev1.PodTemplateSpec{
    78  				ObjectMeta: metav1.ObjectMeta{
    79  					Name:   coreDNSKey,
    80  					Labels: map[string]string{"app": coreDNSKey},
    81  				},
    82  				Spec: corev1.PodSpec{
    83  					Containers: []corev1.Container{{
    84  						Name:  coreDNSKey,
    85  						Image: "k8s.gcr.io/some-folder/coredns:1.6.2",
    86  					}},
    87  				},
    88  			},
    89  			Selector: &metav1.LabelSelector{
    90  				MatchLabels: map[string]string{"app": coreDNSKey},
    91  			},
    92  		},
    93  	}
    94  
    95  	deplWithImage := func(image string) *appsv1.Deployment {
    96  		d := depl.DeepCopy()
    97  		d.Spec.Template.Spec.Containers[0].Image = image
    98  		return d
    99  	}
   100  
   101  	expectedCorefile := "coredns-core-file"
   102  	cm := &corev1.ConfigMap{
   103  		ObjectMeta: metav1.ObjectMeta{
   104  			Name:      coreDNSKey,
   105  			Namespace: metav1.NamespaceSystem,
   106  		},
   107  		Data: map[string]string{
   108  			"Corefile": expectedCorefile,
   109  		},
   110  	}
   111  	updatedCM := &corev1.ConfigMap{
   112  		ObjectMeta: metav1.ObjectMeta{
   113  			Name:      coreDNSKey,
   114  			Namespace: metav1.NamespaceSystem,
   115  		},
   116  		Data: map[string]string{
   117  			"Corefile":        "updated-core-file",
   118  			"Corefile-backup": expectedCorefile,
   119  		},
   120  	}
   121  	kubeadmCM := &corev1.ConfigMap{
   122  		ObjectMeta: metav1.ObjectMeta{
   123  			Name:      kubeadmConfigKey,
   124  			Namespace: metav1.NamespaceSystem,
   125  		},
   126  		Data: map[string]string{
   127  			"ClusterConfiguration": utilyaml.Raw(`
   128  				apiServer:
   129  				apiVersion: kubeadm.k8s.io/v1beta2
   130  				dns:
   131  				  type: CoreDNS
   132  				imageRepository: k8s.gcr.io
   133  				kind: ClusterConfiguration
   134  				`),
   135  		},
   136  	}
   137  	kubeadmCM181 := &corev1.ConfigMap{
   138  		ObjectMeta: metav1.ObjectMeta{
   139  			Name:      kubeadmConfigKey,
   140  			Namespace: metav1.NamespaceSystem,
   141  		},
   142  		Data: map[string]string{
   143  			"ClusterConfiguration": utilyaml.Raw(`
   144  				apiServer:
   145  				apiVersion: kubeadm.k8s.io/v1beta2
   146  				dns:
   147  				  type: CoreDNS
   148  					imageTag: v1.8.1
   149  				imageRepository: k8s.gcr.io
   150  				kind: ClusterConfiguration
   151  				`),
   152  		},
   153  	}
   154  
   155  	oldCR := &rbacv1.ClusterRole{
   156  		TypeMeta: metav1.TypeMeta{
   157  			Kind:       "ClusterRole",
   158  			APIVersion: "rbac.authorization.k8s.io/v1",
   159  		},
   160  		ObjectMeta: metav1.ObjectMeta{
   161  			Name: coreDNSClusterRoleName,
   162  		},
   163  	}
   164  
   165  	semver1191 := semver.MustParse("1.19.1")
   166  	semver1221 := semver.MustParse("1.22.1")
   167  	semver1230 := semver.MustParse("1.23.0")
   168  
   169  	tests := []struct {
   170  		name          string
   171  		kcp           *controlplanev1.KubeadmControlPlane
   172  		migrator      coreDNSMigrator
   173  		semver        semver.Version
   174  		objs          []client.Object
   175  		expectErr     bool
   176  		expectUpdates bool
   177  		expectImage   string
   178  		expectRules   []rbacv1.PolicyRule
   179  	}{
   180  		{
   181  			name: "returns early without error if skip core dns annotation is present",
   182  			kcp: &controlplanev1.KubeadmControlPlane{
   183  				ObjectMeta: metav1.ObjectMeta{
   184  					Annotations: map[string]string{
   185  						controlplanev1.SkipCoreDNSAnnotation: "",
   186  					},
   187  				},
   188  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   189  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   190  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   191  							DNS: bootstrapv1.DNS{},
   192  						},
   193  					},
   194  				},
   195  			},
   196  			semver:    semver1191,
   197  			objs:      []client.Object{badCM},
   198  			expectErr: false,
   199  		},
   200  		{
   201  			name: "returns early without error if KCP ClusterConfiguration is nil",
   202  			kcp: &controlplanev1.KubeadmControlPlane{
   203  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   204  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{},
   205  				},
   206  			},
   207  			semver:    semver1191,
   208  			objs:      []client.Object{badCM},
   209  			expectErr: false,
   210  		},
   211  		{
   212  			name:      "returns early without error if CoreDNS info is not found",
   213  			kcp:       validKCP,
   214  			semver:    semver1191,
   215  			expectErr: false,
   216  		},
   217  		{
   218  			name:      "returns error if there was a problem retrieving CoreDNS info",
   219  			kcp:       validKCP,
   220  			semver:    semver1191,
   221  			objs:      []client.Object{badCM},
   222  			expectErr: true,
   223  		},
   224  		{
   225  			name:      "returns early without error if CoreDNS fromImage == ToImage",
   226  			kcp:       validKCP,
   227  			semver:    semver1191,
   228  			objs:      []client.Object{depl, cm},
   229  			expectErr: false,
   230  		},
   231  		{
   232  			name: "returns error if validation of CoreDNS image tag fails",
   233  			kcp: &controlplanev1.KubeadmControlPlane{
   234  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   235  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   236  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   237  							DNS: bootstrapv1.DNS{
   238  								ImageMeta: bootstrapv1.ImageMeta{
   239  									// image is older than what's already
   240  									// installed.
   241  									ImageRepository: "k8s.gcr.io/some-folder/coredns",
   242  									ImageTag:        "1.1.2",
   243  								},
   244  							},
   245  						},
   246  					},
   247  				},
   248  			},
   249  			semver:    semver1191,
   250  			objs:      []client.Object{depl, cm},
   251  			expectErr: true,
   252  		},
   253  		{
   254  			name: "returns error if unable to update CoreDNS image info in kubeadm config map",
   255  			kcp: &controlplanev1.KubeadmControlPlane{
   256  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   257  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   258  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   259  							DNS: bootstrapv1.DNS{
   260  								ImageMeta: bootstrapv1.ImageMeta{
   261  									// provide an newer image to update to
   262  									ImageRepository: "k8s.gcr.io/some-folder/coredns",
   263  									ImageTag:        "1.7.2",
   264  								},
   265  							},
   266  						},
   267  					},
   268  				},
   269  			},
   270  			semver: semver1191,
   271  			// no kubeadmConfigMap available so it will trigger an error
   272  			objs:      []client.Object{depl, cm},
   273  			expectErr: true,
   274  		},
   275  		{
   276  			name: "returns error if unable to update CoreDNS corefile",
   277  			kcp: &controlplanev1.KubeadmControlPlane{
   278  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   279  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   280  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   281  							DNS: bootstrapv1.DNS{
   282  								ImageMeta: bootstrapv1.ImageMeta{
   283  									// provide an newer image to update to
   284  									ImageRepository: "k8s.gcr.io/some-folder/coredns",
   285  									ImageTag:        "1.7.2",
   286  								},
   287  							},
   288  						},
   289  					},
   290  				},
   291  			},
   292  			migrator: &fakeMigrator{
   293  				migrateErr: errors.New("failed to migrate"),
   294  			},
   295  			semver:    semver1191,
   296  			objs:      []client.Object{depl, cm, kubeadmCM},
   297  			expectErr: true,
   298  		},
   299  		{
   300  			name: "updates everything successfully",
   301  			kcp: &controlplanev1.KubeadmControlPlane{
   302  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   303  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   304  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   305  							DNS: bootstrapv1.DNS{
   306  								ImageMeta: bootstrapv1.ImageMeta{
   307  									// provide an newer image to update to
   308  									ImageRepository: "k8s.gcr.io/some-repo",
   309  									ImageTag:        "1.7.2",
   310  								},
   311  							},
   312  						},
   313  					},
   314  				},
   315  			},
   316  			migrator: &fakeMigrator{
   317  				migratedCorefile: "updated-core-file",
   318  			},
   319  			semver:        semver1191,
   320  			objs:          []client.Object{depl, cm, kubeadmCM},
   321  			expectErr:     false,
   322  			expectUpdates: true,
   323  			expectImage:   "k8s.gcr.io/some-repo/coredns:1.7.2",
   324  		},
   325  		{
   326  			name: "updates everything successfully to v1.8.0 with a custom repo should not change the image name",
   327  			kcp: &controlplanev1.KubeadmControlPlane{
   328  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   329  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   330  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   331  							DNS: bootstrapv1.DNS{
   332  								ImageMeta: bootstrapv1.ImageMeta{
   333  									// provide an newer image to update to
   334  									ImageRepository: "k8s.gcr.io/some-repo",
   335  									ImageTag:        "1.8.0",
   336  								},
   337  							},
   338  						},
   339  					},
   340  				},
   341  			},
   342  			migrator: &fakeMigrator{
   343  				migratedCorefile: "updated-core-file",
   344  			},
   345  			semver:        semver1191,
   346  			objs:          []client.Object{deplWithImage("k8s.gcr.io/some-repo/coredns:1.7.0"), cm, kubeadmCM},
   347  			expectErr:     false,
   348  			expectUpdates: true,
   349  			expectImage:   "k8s.gcr.io/some-repo/coredns:1.8.0",
   350  		},
   351  		{
   352  			name: "kubeadm defaults, upgrade from Kubernetes v1.18.x to v1.19.y (from k8s.gcr.io/coredns:1.6.7 to k8s.gcr.io/coredns:1.7.0)",
   353  			kcp: &controlplanev1.KubeadmControlPlane{
   354  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   355  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   356  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   357  							DNS: bootstrapv1.DNS{
   358  								ImageMeta: bootstrapv1.ImageMeta{
   359  									ImageRepository: "k8s.gcr.io",
   360  									ImageTag:        "1.7.0",
   361  								},
   362  							},
   363  						},
   364  					},
   365  				},
   366  			},
   367  			migrator: &fakeMigrator{
   368  				migratedCorefile: "updated-core-file",
   369  			},
   370  			semver:        semver1191,
   371  			objs:          []client.Object{deplWithImage("k8s.gcr.io/coredns:1.6.7"), cm, kubeadmCM},
   372  			expectErr:     false,
   373  			expectUpdates: true,
   374  			expectImage:   "k8s.gcr.io/coredns:1.7.0",
   375  		},
   376  		{
   377  			name: "kubeadm defaults, upgrade from Kubernetes v1.19.x to v1.20.y (stay on k8s.gcr.io/coredns:1.7.0)",
   378  			kcp: &controlplanev1.KubeadmControlPlane{
   379  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   380  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   381  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   382  							DNS: bootstrapv1.DNS{
   383  								ImageMeta: bootstrapv1.ImageMeta{
   384  									ImageRepository: "k8s.gcr.io",
   385  									ImageTag:        "1.7.0",
   386  								},
   387  							},
   388  						},
   389  					},
   390  				},
   391  			},
   392  			migrator: &fakeMigrator{
   393  				migratedCorefile: "updated-core-file",
   394  			},
   395  			semver:        semver1191,
   396  			objs:          []client.Object{deplWithImage("k8s.gcr.io/coredns:1.7.0"), cm, kubeadmCM},
   397  			expectErr:     false,
   398  			expectUpdates: false,
   399  		},
   400  		{
   401  			name: "kubeadm defaults, upgrade from Kubernetes v1.20.x to v1.21.y (from k8s.gcr.io/coredns:1.7.0 to k8s.gcr.io/coredns/coredns:v1.8.0)",
   402  			kcp: &controlplanev1.KubeadmControlPlane{
   403  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   404  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   405  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   406  							DNS: bootstrapv1.DNS{
   407  								ImageMeta: bootstrapv1.ImageMeta{
   408  									ImageRepository: "k8s.gcr.io",
   409  									ImageTag:        "v1.8.0", // NOTE: ImageTags requires the v prefix
   410  								},
   411  							},
   412  						},
   413  					},
   414  				},
   415  			},
   416  			migrator: &fakeMigrator{
   417  				migratedCorefile: "updated-core-file",
   418  			},
   419  			semver:        semver1191,
   420  			objs:          []client.Object{deplWithImage("k8s.gcr.io/coredns:1.7.0"), cm, kubeadmCM},
   421  			expectErr:     false,
   422  			expectUpdates: true,
   423  			expectImage:   "k8s.gcr.io/coredns/coredns:v1.8.0", // NOTE: ImageName has coredns/coredns
   424  		},
   425  		{
   426  			name: "kubeadm defaults, upgrade from Kubernetes v1.21.x to v1.22.y (stay on k8s.gcr.io/coredns/coredns:v1.8.0)",
   427  			kcp: &controlplanev1.KubeadmControlPlane{
   428  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   429  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   430  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   431  							DNS: bootstrapv1.DNS{
   432  								ImageMeta: bootstrapv1.ImageMeta{
   433  									ImageRepository: "k8s.gcr.io",
   434  									ImageTag:        "v1.8.0", // NOTE: ImageTags requires the v prefix
   435  								},
   436  							},
   437  						},
   438  					},
   439  				},
   440  			},
   441  			semver: semver1191,
   442  			migrator: &fakeMigrator{
   443  				migratedCorefile: "updated-core-file",
   444  			},
   445  			objs:          []client.Object{deplWithImage("k8s.gcr.io/coredns/coredns:v1.8.0"), cm, kubeadmCM},
   446  			expectErr:     false,
   447  			expectUpdates: false,
   448  			expectRules:   oldCR.Rules,
   449  		},
   450  		{
   451  			name: "upgrade from Kubernetes v1.21.x to v1.22.y and update cluster role",
   452  			kcp: &controlplanev1.KubeadmControlPlane{
   453  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   454  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   455  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   456  							DNS: bootstrapv1.DNS{
   457  								ImageMeta: bootstrapv1.ImageMeta{
   458  									ImageRepository: "k8s.gcr.io",
   459  									ImageTag:        "v1.8.1", // NOTE: ImageTags requires the v prefix
   460  								},
   461  							},
   462  						},
   463  					},
   464  				},
   465  			},
   466  			migrator: &fakeMigrator{
   467  				migratedCorefile: "updated-core-file",
   468  			},
   469  			semver:        semver1221,
   470  			objs:          []client.Object{deplWithImage("k8s.gcr.io/coredns/coredns:v1.8.1"), updatedCM, kubeadmCM181, oldCR},
   471  			expectErr:     false,
   472  			expectUpdates: true,
   473  			expectImage:   "k8s.gcr.io/coredns/coredns:v1.8.1", // NOTE: ImageName has coredns/coredns
   474  			expectRules:   coreDNS181PolicyRules,
   475  		},
   476  		{
   477  			name: "returns early without error if kubernetes version is >= v1.23",
   478  			kcp: &controlplanev1.KubeadmControlPlane{
   479  				Spec: controlplanev1.KubeadmControlPlaneSpec{
   480  					KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   481  						ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   482  							DNS: bootstrapv1.DNS{
   483  								ImageMeta: bootstrapv1.ImageMeta{
   484  									ImageRepository: "k8s.gcr.io",
   485  									ImageTag:        "v1.8.1", // NOTE: ImageTags requires the v prefix
   486  								},
   487  							},
   488  						},
   489  					},
   490  				},
   491  			},
   492  			semver:      semver1230,
   493  			objs:        []client.Object{deplWithImage("k8s.gcr.io/coredns/coredns:v1.8.1"), updatedCM, kubeadmCM181},
   494  			expectErr:   false,
   495  			expectRules: oldCR.Rules,
   496  		},
   497  	}
   498  
   499  	// We are using testEnv as a workload cluster, and given that each test case assumes well known objects with specific
   500  	// Namespace/Name (e.g. The CoderDNS ConfigMap & Deployment, the kubeadm ConfigMap), it is not possible to run the use cases in parallel.
   501  	for _, tt := range tests {
   502  		t.Run(tt.name, func(t *testing.T) {
   503  			g := NewWithT(t)
   504  
   505  			for _, o := range tt.objs {
   506  				// NB. deep copy test object so changes applied during a test does not affect other tests.
   507  				o := o.DeepCopyObject().(client.Object)
   508  				g.Expect(env.CreateAndWait(ctx, o)).To(Succeed())
   509  			}
   510  
   511  			// Register cleanup function
   512  			t.Cleanup(func() {
   513  				_ = env.CleanupAndWait(ctx, tt.objs...)
   514  			})
   515  
   516  			w := &Workload{
   517  				Client:          env.GetClient(),
   518  				CoreDNSMigrator: tt.migrator,
   519  			}
   520  			err := w.UpdateCoreDNS(ctx, tt.kcp, tt.semver)
   521  
   522  			if tt.expectErr {
   523  				g.Expect(err).To(HaveOccurred())
   524  				return
   525  			}
   526  			g.Expect(err).ToNot(HaveOccurred())
   527  
   528  			// Assert that CoreDNS updates have been made
   529  			if tt.expectUpdates {
   530  				// assert kubeadmConfigMap
   531  				g.Eventually(func(g Gomega) error {
   532  					var expectedKubeadmConfigMap corev1.ConfigMap
   533  					g.Expect(env.Get(ctx, client.ObjectKey{Name: kubeadmConfigKey, Namespace: metav1.NamespaceSystem}, &expectedKubeadmConfigMap)).To(Succeed())
   534  					g.Expect(expectedKubeadmConfigMap.Data).To(HaveKeyWithValue("ClusterConfiguration", ContainSubstring(tt.kcp.Spec.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageTag)))
   535  					g.Expect(expectedKubeadmConfigMap.Data).To(HaveKeyWithValue("ClusterConfiguration", ContainSubstring(tt.kcp.Spec.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageRepository)))
   536  					return nil
   537  				}, "5s").Should(Succeed())
   538  
   539  				// assert CoreDNS corefile
   540  				var expectedConfigMap corev1.ConfigMap
   541  				g.Eventually(func() error {
   542  					if err := env.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &expectedConfigMap); err != nil {
   543  						return errors.Wrap(err, "failed to get the coredns ConfigMap")
   544  					}
   545  					if len(expectedConfigMap.Data) != 2 {
   546  						return errors.Errorf("the coredns ConfigMap has %d data items, expected 2", len(expectedConfigMap.Data))
   547  					}
   548  					if val, ok := expectedConfigMap.Data["Corefile"]; !ok || val != "updated-core-file" {
   549  						return errors.New("the coredns ConfigMap does not have the Corefile entry or this it has an unexpected value")
   550  					}
   551  					if val, ok := expectedConfigMap.Data["Corefile-backup"]; !ok || val != expectedCorefile {
   552  						return errors.New("the coredns ConfigMap does not have the Corefile-backup entry or this it has an unexpected value")
   553  					}
   554  					return nil
   555  				}, "5s").Should(BeNil())
   556  
   557  				// assert CoreDNS deployment
   558  				var actualDeployment appsv1.Deployment
   559  				g.Eventually(func() string {
   560  					g.Expect(env.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &actualDeployment)).To(Succeed())
   561  					return actualDeployment.Spec.Template.Spec.Containers[0].Image
   562  				}, "5s").Should(Equal(tt.expectImage))
   563  
   564  				// assert CoreDNS ClusterRole
   565  				if tt.expectRules != nil {
   566  					var actualClusterRole rbacv1.ClusterRole
   567  					g.Eventually(func() []rbacv1.PolicyRule {
   568  						g.Expect(env.Get(ctx, client.ObjectKey{Name: coreDNSClusterRoleName}, &actualClusterRole)).To(Succeed())
   569  						return actualClusterRole.Rules
   570  					}, "5s").Should(BeComparableTo(tt.expectRules))
   571  				}
   572  			}
   573  		})
   574  	}
   575  }
   576  
   577  func TestValidateCoreDNSImageTag(t *testing.T) {
   578  	tests := []struct {
   579  		name            string
   580  		fromVer         string
   581  		toVer           string
   582  		expectErrSubStr string
   583  	}{
   584  		{
   585  			name:            "fromVer is higher than toVer",
   586  			fromVer:         "1.6.2",
   587  			toVer:           "1.1.3",
   588  			expectErrSubStr: "must be greater than",
   589  		},
   590  		{
   591  			name:            "fromVer is not a valid coredns version",
   592  			fromVer:         "0.204.123",
   593  			toVer:           "1.6.3",
   594  			expectErrSubStr: "not a compatible coredns version",
   595  		},
   596  		{
   597  			name:            "toVer is not a valid semver",
   598  			fromVer:         "1.5.1",
   599  			toVer:           "foobar",
   600  			expectErrSubStr: "failed to parse CoreDNS target version",
   601  		},
   602  		{
   603  			name:            "fromVer is not a valid semver",
   604  			fromVer:         "foobar",
   605  			toVer:           "1.6.1",
   606  			expectErrSubStr: "failed to parse CoreDNS current version",
   607  		},
   608  		{
   609  			name:    "fromVer is equal to toVer, but different patch versions",
   610  			fromVer: "1.6.5_foobar.1",
   611  			toVer:   "1.6.5_foobar.2",
   612  		},
   613  		{
   614  			name:    "fromVer is equal to toVer",
   615  			fromVer: "1.6.5_foobar.1",
   616  			toVer:   "1.6.5_foobar.1",
   617  		},
   618  		{
   619  			name:    "fromVer is lower but has meta",
   620  			fromVer: "1.6.5-foobar.1",
   621  			toVer:   "1.7.5",
   622  		},
   623  		{
   624  			name:    "fromVer is lower and has meta and leading v",
   625  			fromVer: "v1.6.5-foobar.1",
   626  			toVer:   "1.7.5",
   627  		},
   628  		{
   629  			name:    "fromVer is lower, toVer has meta and leading v",
   630  			fromVer: "1.6.5-foobar.1",
   631  			toVer:   "v1.7.5_foobar.1",
   632  		},
   633  	}
   634  
   635  	for _, tt := range tests {
   636  		t.Run(tt.name, func(t *testing.T) {
   637  			g := NewWithT(t)
   638  			err := validateCoreDNSImageTag(tt.fromVer, tt.toVer)
   639  			if tt.expectErrSubStr != "" {
   640  				g.Expect(err.Error()).To(ContainSubstring(tt.expectErrSubStr))
   641  			} else {
   642  				g.Expect(err).ToNot(HaveOccurred())
   643  			}
   644  		})
   645  	}
   646  }
   647  
   648  func TestUpdateCoreDNSClusterRole(t *testing.T) {
   649  	coreDNS180PolicyRules := []rbacv1.PolicyRule{
   650  		{
   651  			Verbs:     []string{"list", "watch"},
   652  			APIGroups: []string{""},
   653  			Resources: []string{"endpoints", "services", "pods", "namespaces"},
   654  		},
   655  		{
   656  			Verbs:     []string{"get"},
   657  			APIGroups: []string{""},
   658  			Resources: []string{"nodes"},
   659  		},
   660  	}
   661  
   662  	tests := []struct {
   663  		name                     string
   664  		kubernetesVersion        semver.Version
   665  		coreDNSVersion           string
   666  		sourceCoreDNSVersion     string
   667  		coreDNSPolicyRules       []rbacv1.PolicyRule
   668  		expectErr                bool
   669  		expectCoreDNSPolicyRules []rbacv1.PolicyRule
   670  	}{
   671  		{
   672  			name:               "does not patch ClusterRole: invalid CoreDNS tag",
   673  			kubernetesVersion:  semver.Version{Major: 1, Minor: 22, Patch: 0},
   674  			coreDNSVersion:     "no-semver",
   675  			coreDNSPolicyRules: coreDNS180PolicyRules,
   676  			expectErr:          true,
   677  		},
   678  		{
   679  			name:                     "does not patch ClusterRole: Kubernetes < 1.22",
   680  			kubernetesVersion:        semver.Version{Major: 1, Minor: 21, Patch: 0},
   681  			coreDNSVersion:           "1.8.4",
   682  			coreDNSPolicyRules:       coreDNS180PolicyRules,
   683  			expectCoreDNSPolicyRules: coreDNS180PolicyRules,
   684  		},
   685  		{
   686  			name:                     "does not patch ClusterRole: Kubernetes > 1.22 && CoreDNS >= 1.8.1",
   687  			kubernetesVersion:        semver.Version{Major: 1, Minor: 23, Patch: 0},
   688  			coreDNSVersion:           "1.8.1",
   689  			sourceCoreDNSVersion:     "1.8.1",
   690  			coreDNSPolicyRules:       coreDNS180PolicyRules,
   691  			expectCoreDNSPolicyRules: coreDNS180PolicyRules,
   692  		},
   693  		{
   694  			name:                     "does not patch ClusterRole: CoreDNS < 1.8.1",
   695  			kubernetesVersion:        semver.Version{Major: 1, Minor: 22, Patch: 0},
   696  			coreDNSVersion:           "1.8.0",
   697  			sourceCoreDNSVersion:     "1.7.0",
   698  			coreDNSPolicyRules:       coreDNS180PolicyRules,
   699  			expectCoreDNSPolicyRules: coreDNS180PolicyRules,
   700  		},
   701  		{
   702  			name:                     "patch ClusterRole: Kubernetes == 1.22 alpha and CoreDNS == 1.8.1",
   703  			kubernetesVersion:        semver.Version{Major: 1, Minor: 22, Patch: 0, Pre: []semver.PRVersion{{VersionStr: "alpha"}}},
   704  			coreDNSVersion:           "1.8.1",
   705  			sourceCoreDNSVersion:     "1.8.1",
   706  			coreDNSPolicyRules:       coreDNS180PolicyRules,
   707  			expectCoreDNSPolicyRules: coreDNS181PolicyRules,
   708  		},
   709  		{
   710  			name:                     "patch ClusterRole: Kubernetes == 1.22 and CoreDNS == 1.8.1",
   711  			kubernetesVersion:        semver.Version{Major: 1, Minor: 22, Patch: 0},
   712  			coreDNSVersion:           "1.8.1",
   713  			sourceCoreDNSVersion:     "1.8.1",
   714  			coreDNSPolicyRules:       coreDNS180PolicyRules,
   715  			expectCoreDNSPolicyRules: coreDNS181PolicyRules,
   716  		},
   717  		{
   718  			name:                     "patch ClusterRole: Kubernetes > 1.22 and CoreDNS > 1.8.1",
   719  			kubernetesVersion:        semver.Version{Major: 1, Minor: 22, Patch: 2},
   720  			coreDNSVersion:           "1.8.5",
   721  			sourceCoreDNSVersion:     "1.8.1",
   722  			coreDNSPolicyRules:       coreDNS180PolicyRules,
   723  			expectCoreDNSPolicyRules: coreDNS181PolicyRules,
   724  		},
   725  		{
   726  			name:                     "patch ClusterRole: Kubernetes > 1.22 and CoreDNS > 1.8.1: no-op",
   727  			kubernetesVersion:        semver.Version{Major: 1, Minor: 22, Patch: 2},
   728  			coreDNSVersion:           "1.8.5",
   729  			sourceCoreDNSVersion:     "1.8.5",
   730  			coreDNSPolicyRules:       coreDNS181PolicyRules,
   731  			expectCoreDNSPolicyRules: coreDNS181PolicyRules,
   732  		},
   733  	}
   734  
   735  	for _, tt := range tests {
   736  		t.Run(tt.name, func(t *testing.T) {
   737  			g := NewWithT(t)
   738  
   739  			cr := &rbacv1.ClusterRole{
   740  				ObjectMeta: metav1.ObjectMeta{
   741  					Name:      coreDNSClusterRoleName,
   742  					Namespace: metav1.NamespaceSystem,
   743  				},
   744  				Rules: tt.coreDNSPolicyRules,
   745  			}
   746  			fakeClient := fake.NewClientBuilder().WithObjects(cr).Build()
   747  
   748  			w := &Workload{
   749  				Client: fakeClient,
   750  			}
   751  
   752  			err := w.updateCoreDNSClusterRole(ctx, tt.kubernetesVersion, &coreDNSInfo{ToImageTag: tt.coreDNSVersion, FromImageTag: tt.sourceCoreDNSVersion})
   753  
   754  			if tt.expectErr {
   755  				g.Expect(err).To(HaveOccurred())
   756  				return
   757  			}
   758  			g.Expect(err).ToNot(HaveOccurred())
   759  
   760  			var actualClusterRole rbacv1.ClusterRole
   761  			g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSClusterRoleName, Namespace: metav1.NamespaceSystem}, &actualClusterRole)).To(Succeed())
   762  
   763  			g.Expect(actualClusterRole.Rules).To(BeComparableTo(tt.expectCoreDNSPolicyRules))
   764  		})
   765  	}
   766  }
   767  
   768  func TestSemanticallyDeepEqualPolicyRules(t *testing.T) {
   769  	tests := []struct {
   770  		name string
   771  		r1   []rbacv1.PolicyRule
   772  		r2   []rbacv1.PolicyRule
   773  		want bool
   774  	}{
   775  		{
   776  			name: "equal: identical arrays",
   777  			r1: []rbacv1.PolicyRule{
   778  				{
   779  					Verbs:     []string{"list", "watch"},
   780  					APIGroups: []string{""},
   781  					Resources: []string{"endpoints", "services", "pods", "namespaces"},
   782  				},
   783  				{
   784  					Verbs:     []string{"list", "watch"},
   785  					APIGroups: []string{"discovery.k8s.io"},
   786  					Resources: []string{"endpointslices"},
   787  				},
   788  			},
   789  			r2: []rbacv1.PolicyRule{
   790  				{
   791  					Verbs:     []string{"list", "watch"},
   792  					APIGroups: []string{""},
   793  					Resources: []string{"endpoints", "services", "pods", "namespaces"},
   794  				},
   795  				{
   796  					Verbs:     []string{"list", "watch"},
   797  					APIGroups: []string{"discovery.k8s.io"},
   798  					Resources: []string{"endpointslices"},
   799  				},
   800  			},
   801  			want: true,
   802  		},
   803  		{
   804  			name: "equal: arrays with different order",
   805  			r1: []rbacv1.PolicyRule{
   806  				{
   807  					Verbs:     []string{"list", "watch"},
   808  					APIGroups: []string{""},
   809  					Resources: []string{"endpoints", "services", "pods", "namespaces"},
   810  				},
   811  				{
   812  					Verbs:     []string{"list", "watch"},
   813  					APIGroups: []string{"discovery.k8s.io"},
   814  					Resources: []string{"endpointslices"},
   815  				},
   816  			},
   817  			r2: []rbacv1.PolicyRule{
   818  				{
   819  					Verbs:     []string{"watch", "list"},
   820  					APIGroups: []string{"discovery.k8s.io"},
   821  					Resources: []string{"endpointslices"},
   822  				},
   823  				{
   824  					Verbs:     []string{"list", "watch"},
   825  					APIGroups: []string{""},
   826  					Resources: []string{"endpoints", "pods", "services", "namespaces"},
   827  				},
   828  			},
   829  			want: true,
   830  		},
   831  		{
   832  			name: "equal: separate rules but same semantic",
   833  			r1: []rbacv1.PolicyRule{
   834  				{
   835  					Verbs:     []string{"list", "watch"},
   836  					APIGroups: []string{""},
   837  					Resources: []string{"endpoints", "services", "pods", "namespaces"},
   838  				},
   839  				{
   840  					Verbs:     []string{"list", "watch"},
   841  					APIGroups: []string{"discovery.k8s.io"},
   842  					Resources: []string{"endpointslices"},
   843  				},
   844  			},
   845  			r2: []rbacv1.PolicyRule{
   846  				{
   847  					Verbs:     []string{"watch", "list"},
   848  					APIGroups: []string{"discovery.k8s.io"},
   849  					Resources: []string{"endpointslices"},
   850  				},
   851  				{
   852  					Verbs:     []string{"list", "watch"},
   853  					APIGroups: []string{""},
   854  					Resources: []string{"endpoints", "pods"},
   855  				},
   856  				{
   857  					Verbs:     []string{"list", "watch"},
   858  					APIGroups: []string{""},
   859  					Resources: []string{"services"},
   860  				},
   861  				{
   862  					Verbs:     []string{"list", "watch"},
   863  					APIGroups: []string{""},
   864  					Resources: []string{"namespaces"},
   865  				},
   866  			},
   867  			want: true,
   868  		},
   869  		{
   870  			name: "not equal: one array has additional rules",
   871  			r1: []rbacv1.PolicyRule{
   872  				{
   873  					Verbs:     []string{"list", "watch"},
   874  					APIGroups: []string{""},
   875  					Resources: []string{"endpoints", "services", "pods", "namespaces"},
   876  				},
   877  				{
   878  					Verbs:     []string{"list", "watch"},
   879  					APIGroups: []string{"discovery.k8s.io"},
   880  					Resources: []string{"endpointslices"},
   881  				},
   882  			},
   883  			r2: []rbacv1.PolicyRule{
   884  				{
   885  					Verbs:     []string{"list", "watch"},
   886  					APIGroups: []string{""},
   887  					Resources: []string{"endpoints", "services", "pods", "namespaces"},
   888  				},
   889  				{
   890  					Verbs:     []string{"list", "watch"},
   891  					APIGroups: []string{"discovery.k8s.io"},
   892  					Resources: []string{"endpointslices"},
   893  				},
   894  				{
   895  					Verbs:     []string{"get"},
   896  					APIGroups: []string{""},
   897  					Resources: []string{"nodes"},
   898  				},
   899  			},
   900  			want: false,
   901  		},
   902  		{
   903  			name: "not equal: one array has additional verbs",
   904  			r1: []rbacv1.PolicyRule{
   905  				{
   906  					Verbs:     []string{"list", "watch"},
   907  					APIGroups: []string{""},
   908  					Resources: []string{"endpoints", "services", "pods", "namespaces"},
   909  				},
   910  				{
   911  					Verbs:     []string{"list", "watch"},
   912  					APIGroups: []string{"discovery.k8s.io"},
   913  					Resources: []string{"endpointslices"},
   914  				},
   915  			},
   916  			r2: []rbacv1.PolicyRule{
   917  				{
   918  					Verbs:     []string{"list", "watch"},
   919  					APIGroups: []string{""},
   920  					Resources: []string{"endpoints", "services", "pods", "namespaces"},
   921  				},
   922  				{
   923  					Verbs:     []string{"list", "watch", "get", "update"},
   924  					APIGroups: []string{"discovery.k8s.io"},
   925  					Resources: []string{"endpointslices"},
   926  				},
   927  			},
   928  			want: false,
   929  		},
   930  	}
   931  	for _, tt := range tests {
   932  		t.Run(tt.name, func(t *testing.T) {
   933  			if got := semanticDeepEqualPolicyRules(tt.r1, tt.r2); got != tt.want {
   934  				t.Errorf("semanticDeepEqualPolicyRules() = %v, want %v", got, tt.want)
   935  			}
   936  		})
   937  	}
   938  }
   939  
   940  func TestUpdateCoreDNSCorefile(t *testing.T) {
   941  	currentImageTag := "1.6.2"
   942  	originalCorefile := "some-coredns-core-file"
   943  	depl := &appsv1.Deployment{
   944  		ObjectMeta: metav1.ObjectMeta{
   945  			Name:      coreDNSKey,
   946  			Namespace: metav1.NamespaceSystem,
   947  		},
   948  		Spec: appsv1.DeploymentSpec{
   949  			Template: corev1.PodTemplateSpec{
   950  				ObjectMeta: metav1.ObjectMeta{
   951  					Name: coreDNSKey,
   952  				},
   953  				Spec: corev1.PodSpec{
   954  					Containers: []corev1.Container{{
   955  						Name:  coreDNSKey,
   956  						Image: "k8s.gcr.io/coredns:" + currentImageTag,
   957  					}},
   958  					Volumes: []corev1.Volume{{
   959  						Name: "config-volume",
   960  						VolumeSource: corev1.VolumeSource{
   961  							ConfigMap: &corev1.ConfigMapVolumeSource{
   962  								LocalObjectReference: corev1.LocalObjectReference{
   963  									Name: coreDNSKey,
   964  								},
   965  								Items: []corev1.KeyToPath{{
   966  									Key:  "Corefile",
   967  									Path: "Corefile",
   968  								}},
   969  							},
   970  						},
   971  					}},
   972  				},
   973  			},
   974  		},
   975  	}
   976  	cm := &corev1.ConfigMap{
   977  		ObjectMeta: metav1.ObjectMeta{
   978  			Name:      coreDNSKey,
   979  			Namespace: metav1.NamespaceSystem,
   980  		},
   981  		Data: map[string]string{
   982  			"Corefile": originalCorefile,
   983  		},
   984  	}
   985  
   986  	t.Run("returns error if migrate failed to update corefile", func(t *testing.T) {
   987  		g := NewWithT(t)
   988  		objs := []client.Object{depl, cm}
   989  		fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build()
   990  		fakeMigrator := &fakeMigrator{
   991  			migrateErr: errors.New("failed to migrate"),
   992  		}
   993  
   994  		w := &Workload{
   995  			Client:          fakeClient,
   996  			CoreDNSMigrator: fakeMigrator,
   997  		}
   998  
   999  		info := &coreDNSInfo{
  1000  			Corefile:               "updated-core-file",
  1001  			Deployment:             depl,
  1002  			CurrentMajorMinorPatch: "1.6.2",
  1003  			TargetMajorMinorPatch:  "1.7.2",
  1004  		}
  1005  
  1006  		err := w.updateCoreDNSCorefile(ctx, info)
  1007  		g.Expect(err).To(HaveOccurred())
  1008  		g.Expect(fakeMigrator.migrateCalled).To(BeTrue())
  1009  
  1010  		var expectedConfigMap corev1.ConfigMap
  1011  		g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &expectedConfigMap)).To(Succeed())
  1012  		g.Expect(expectedConfigMap.Data).To(HaveLen(1))
  1013  		g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile", originalCorefile))
  1014  	})
  1015  
  1016  	t.Run("creates a backup of the corefile", func(t *testing.T) {
  1017  		g := NewWithT(t)
  1018  		// Not including the deployment so as to fail early and verify that
  1019  		// the intermediate config map update occurred
  1020  		objs := []client.Object{cm}
  1021  		fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build()
  1022  		fakeMigrator := &fakeMigrator{
  1023  			migratedCorefile: "updated-core-file",
  1024  		}
  1025  
  1026  		w := &Workload{
  1027  			Client:          fakeClient,
  1028  			CoreDNSMigrator: fakeMigrator,
  1029  		}
  1030  
  1031  		info := &coreDNSInfo{
  1032  			Corefile:               originalCorefile,
  1033  			Deployment:             depl,
  1034  			CurrentMajorMinorPatch: currentImageTag,
  1035  			TargetMajorMinorPatch:  "1.7.2",
  1036  		}
  1037  
  1038  		err := w.updateCoreDNSCorefile(ctx, info)
  1039  		g.Expect(err).To(HaveOccurred())
  1040  
  1041  		var expectedConfigMap corev1.ConfigMap
  1042  		g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &expectedConfigMap)).To(Succeed())
  1043  		g.Expect(expectedConfigMap.Data).To(HaveLen(2))
  1044  		g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile", originalCorefile))
  1045  		g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile-backup", originalCorefile))
  1046  	})
  1047  
  1048  	t.Run("patches the core dns deployment to point to the backup corefile before migration", func(t *testing.T) {
  1049  		t.Skip("Updating the corefile, after updating controller runtime somehow makes this test fail in a conflict, needs investigation")
  1050  
  1051  		g := NewWithT(t)
  1052  		objs := []client.Object{depl, cm}
  1053  		fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build()
  1054  		fakeMigrator := &fakeMigrator{
  1055  			migratedCorefile: "updated-core-file",
  1056  		}
  1057  
  1058  		w := &Workload{
  1059  			Client:          fakeClient,
  1060  			CoreDNSMigrator: fakeMigrator,
  1061  		}
  1062  
  1063  		info := &coreDNSInfo{
  1064  			Corefile:               originalCorefile,
  1065  			Deployment:             depl,
  1066  			CurrentMajorMinorPatch: currentImageTag,
  1067  			TargetMajorMinorPatch:  "1.7.2",
  1068  		}
  1069  
  1070  		err := w.updateCoreDNSCorefile(ctx, info)
  1071  		g.Expect(err).ToNot(HaveOccurred())
  1072  
  1073  		expectedVolume := corev1.Volume{
  1074  			Name: coreDNSVolumeKey,
  1075  			VolumeSource: corev1.VolumeSource{
  1076  				ConfigMap: &corev1.ConfigMapVolumeSource{
  1077  					LocalObjectReference: corev1.LocalObjectReference{
  1078  						Name: coreDNSKey,
  1079  					},
  1080  					Items: []corev1.KeyToPath{{
  1081  						Key:  "Corefile-backup",
  1082  						Path: "Corefile",
  1083  					}},
  1084  				},
  1085  			},
  1086  		}
  1087  
  1088  		var actualDeployment appsv1.Deployment
  1089  		g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &actualDeployment)).To(Succeed())
  1090  		g.Expect(actualDeployment.Spec.Template.Spec.Volumes).To(ConsistOf(expectedVolume))
  1091  
  1092  		var expectedConfigMap corev1.ConfigMap
  1093  		g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &expectedConfigMap)).To(Succeed())
  1094  		g.Expect(expectedConfigMap.Data).To(HaveLen(2))
  1095  		g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile", "updated-core-file"))
  1096  		g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile-backup", originalCorefile))
  1097  	})
  1098  }
  1099  
  1100  func TestGetCoreDNSInfo(t *testing.T) {
  1101  	t.Run("get coredns info", func(t *testing.T) {
  1102  		imageSomeFolder162 := "k8s.gcr.io/some-folder/coredns:1.6.2"
  1103  		image162 := "k8s.gcr.io/coredns:1.6.2"
  1104  
  1105  		expectedCorefile := "some-coredns-core-file"
  1106  		cm := &corev1.ConfigMap{
  1107  			ObjectMeta: metav1.ObjectMeta{
  1108  				Name:      coreDNSKey,
  1109  				Namespace: metav1.NamespaceSystem,
  1110  			},
  1111  			Data: map[string]string{
  1112  				"Corefile": expectedCorefile,
  1113  			},
  1114  		}
  1115  
  1116  		emptycm := cm.DeepCopy()
  1117  		delete(emptycm.Data, "Corefile")
  1118  
  1119  		emptyDepl := newCoreDNSInfoDeploymentWithimage("")
  1120  		emptyDepl.Spec.Template.Spec.Containers = []corev1.Container{}
  1121  
  1122  		clusterConfig := &bootstrapv1.ClusterConfiguration{
  1123  			DNS: bootstrapv1.DNS{
  1124  				ImageMeta: bootstrapv1.ImageMeta{
  1125  					ImageRepository: "myrepo",
  1126  					ImageTag:        "1.7.2-foobar.1",
  1127  				},
  1128  			},
  1129  		}
  1130  		badImgTagDNS := clusterConfig.DeepCopy()
  1131  		badImgTagDNS.DNS.ImageTag = "v1X6.2-foobar.1"
  1132  
  1133  		tests := []struct {
  1134  			name              string
  1135  			expectErr         bool
  1136  			objs              []client.Object
  1137  			clusterConfig     *bootstrapv1.ClusterConfiguration
  1138  			kubernetesVersion semver.Version
  1139  			expectedInfo      coreDNSInfo
  1140  		}{
  1141  			{
  1142  				name:          "returns core dns info",
  1143  				objs:          []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm},
  1144  				clusterConfig: clusterConfig,
  1145  				expectedInfo: coreDNSInfo{
  1146  					CurrentMajorMinorPatch: "1.6.2",
  1147  					FromImageTag:           "1.6.2",
  1148  					TargetMajorMinorPatch:  "1.7.2",
  1149  					FromImage:              imageSomeFolder162,
  1150  					ToImage:                "myrepo/coredns:1.7.2-foobar.1",
  1151  					ToImageTag:             "1.7.2-foobar.1",
  1152  				},
  1153  			},
  1154  			{
  1155  				name: "uses global config ImageRepository if DNS ImageRepository is not set",
  1156  				objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm},
  1157  				clusterConfig: &bootstrapv1.ClusterConfiguration{
  1158  					ImageRepository: "globalRepo/sub-path",
  1159  					DNS: bootstrapv1.DNS{
  1160  						ImageMeta: bootstrapv1.ImageMeta{
  1161  							ImageTag: "1.7.2-foobar.1",
  1162  						},
  1163  					},
  1164  				},
  1165  				expectedInfo: coreDNSInfo{
  1166  					CurrentMajorMinorPatch: "1.6.2",
  1167  					FromImageTag:           "1.6.2",
  1168  					TargetMajorMinorPatch:  "1.7.2",
  1169  					FromImage:              imageSomeFolder162,
  1170  					ToImage:                "globalRepo/sub-path/coredns:1.7.2-foobar.1",
  1171  					ToImageTag:             "1.7.2-foobar.1",
  1172  				},
  1173  			},
  1174  			{
  1175  				name: "uses DNS ImageRepository config if both global and DNS-level are set",
  1176  				objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm},
  1177  				clusterConfig: &bootstrapv1.ClusterConfiguration{
  1178  					ImageRepository: "globalRepo",
  1179  					DNS: bootstrapv1.DNS{
  1180  						ImageMeta: bootstrapv1.ImageMeta{
  1181  							ImageRepository: "dnsRepo",
  1182  							ImageTag:        "1.7.2-foobar.1",
  1183  						},
  1184  					},
  1185  				},
  1186  				expectedInfo: coreDNSInfo{
  1187  					CurrentMajorMinorPatch: "1.6.2",
  1188  					FromImageTag:           "1.6.2",
  1189  					TargetMajorMinorPatch:  "1.7.2",
  1190  					FromImage:              imageSomeFolder162,
  1191  					ToImage:                "dnsRepo/coredns:1.7.2-foobar.1",
  1192  					ToImageTag:             "1.7.2-foobar.1",
  1193  				},
  1194  			},
  1195  			{
  1196  				name: "patches ImageRepository to registry.k8s.io if it's set on neither global nor DNS-level and kubernetesVersion >= v1.25",
  1197  				objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm},
  1198  				clusterConfig: &bootstrapv1.ClusterConfiguration{
  1199  					DNS: bootstrapv1.DNS{
  1200  						ImageMeta: bootstrapv1.ImageMeta{
  1201  							ImageTag: "1.7.2-foobar.1",
  1202  						},
  1203  					},
  1204  				},
  1205  				kubernetesVersion: semver.MustParse("1.25.0"),
  1206  				expectedInfo: coreDNSInfo{
  1207  					CurrentMajorMinorPatch: "1.6.2",
  1208  					FromImageTag:           "1.6.2",
  1209  					TargetMajorMinorPatch:  "1.7.2",
  1210  					FromImage:              imageSomeFolder162,
  1211  					ToImage:                "registry.k8s.io/some-folder/coredns:1.7.2-foobar.1",
  1212  					ToImageTag:             "1.7.2-foobar.1",
  1213  				},
  1214  			},
  1215  			{
  1216  				name: "rename to coredns/coredns when upgrading to coredns=1.8.0 and kubernetesVersion=1.22.16",
  1217  				// 1.22.16 uses k8s.gcr.io as default registry. Thus the registry doesn't get changed as
  1218  				// FromImage is already using k8s.gcr.io.
  1219  				objs: []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/coredns:1.6.2"), cm},
  1220  				clusterConfig: &bootstrapv1.ClusterConfiguration{
  1221  					DNS: bootstrapv1.DNS{
  1222  						ImageMeta: bootstrapv1.ImageMeta{
  1223  							ImageTag: "1.8.0",
  1224  						},
  1225  					},
  1226  				},
  1227  				kubernetesVersion: semver.MustParse("1.22.16"),
  1228  				expectedInfo: coreDNSInfo{
  1229  					CurrentMajorMinorPatch: "1.6.2",
  1230  					FromImageTag:           "1.6.2",
  1231  					TargetMajorMinorPatch:  "1.8.0",
  1232  					FromImage:              "k8s.gcr.io/coredns:1.6.2",
  1233  					ToImage:                "k8s.gcr.io/coredns/coredns:1.8.0",
  1234  					ToImageTag:             "1.8.0",
  1235  				},
  1236  			},
  1237  			{
  1238  				name: "rename to coredns/coredns when upgrading to coredns=1.8.0 and kubernetesVersion=1.22.17",
  1239  				// 1.22.17 has registry.k8s.io as default registry. Thus the registry gets changed as
  1240  				// FromImage is using k8s.gcr.io.
  1241  				objs: []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/coredns:1.6.2"), cm},
  1242  				clusterConfig: &bootstrapv1.ClusterConfiguration{
  1243  					DNS: bootstrapv1.DNS{
  1244  						ImageMeta: bootstrapv1.ImageMeta{
  1245  							ImageTag: "1.8.0",
  1246  						},
  1247  					},
  1248  				},
  1249  				kubernetesVersion: semver.MustParse("1.22.17"),
  1250  				expectedInfo: coreDNSInfo{
  1251  					CurrentMajorMinorPatch: "1.6.2",
  1252  					FromImageTag:           "1.6.2",
  1253  					TargetMajorMinorPatch:  "1.8.0",
  1254  					FromImage:              "k8s.gcr.io/coredns:1.6.2",
  1255  					ToImage:                "registry.k8s.io/coredns/coredns:1.8.0",
  1256  					ToImageTag:             "1.8.0",
  1257  				},
  1258  			},
  1259  			{
  1260  				name: "rename to coredns/coredns when upgrading to coredns=1.8.0 and kubernetesVersion=1.26.0",
  1261  				// 1.26.0 uses registry.k8s.io as default registry. Thus the registry doesn't get changed as
  1262  				// FromImage is already using registry.k8s.io.
  1263  				objs: []client.Object{newCoreDNSInfoDeploymentWithimage("registry.k8s.io/coredns:1.6.2"), cm},
  1264  				clusterConfig: &bootstrapv1.ClusterConfiguration{
  1265  					DNS: bootstrapv1.DNS{
  1266  						ImageMeta: bootstrapv1.ImageMeta{
  1267  							ImageTag: "1.8.0",
  1268  						},
  1269  					},
  1270  				},
  1271  				kubernetesVersion: semver.MustParse("1.26.0"),
  1272  				expectedInfo: coreDNSInfo{
  1273  					CurrentMajorMinorPatch: "1.6.2",
  1274  					FromImageTag:           "1.6.2",
  1275  					TargetMajorMinorPatch:  "1.8.0",
  1276  					FromImage:              "registry.k8s.io/coredns:1.6.2",
  1277  					ToImage:                "registry.k8s.io/coredns/coredns:1.8.0",
  1278  					ToImageTag:             "1.8.0",
  1279  				},
  1280  			},
  1281  			{
  1282  				name: "patches ImageRepository to registry.k8s.io if it's set on neither global nor DNS-level and kubernetesVersion >= v1.22.17 and rename to coredns/coredns",
  1283  				// 1.22.17 has registry.k8s.io as default registry. Thus the registry gets changed as
  1284  				// FromImage is using k8s.gcr.io.
  1285  				objs: []client.Object{newCoreDNSInfoDeploymentWithimage(image162), cm},
  1286  				clusterConfig: &bootstrapv1.ClusterConfiguration{
  1287  					DNS: bootstrapv1.DNS{
  1288  						ImageMeta: bootstrapv1.ImageMeta{
  1289  							ImageTag: "1.8.0",
  1290  						},
  1291  					},
  1292  				},
  1293  				kubernetesVersion: semver.MustParse("1.22.17"),
  1294  				expectedInfo: coreDNSInfo{
  1295  					CurrentMajorMinorPatch: "1.6.2",
  1296  					FromImageTag:           "1.6.2",
  1297  					TargetMajorMinorPatch:  "1.8.0",
  1298  					FromImage:              image162,
  1299  					ToImage:                "registry.k8s.io/coredns/coredns:1.8.0",
  1300  					ToImageTag:             "1.8.0",
  1301  				},
  1302  			},
  1303  			{
  1304  				name: "patches ImageRepository to registry.k8s.io if it's set on neither global nor DNS-level and kubernetesVersion >= v1.25 and rename to coredns/coredns",
  1305  				objs: []client.Object{newCoreDNSInfoDeploymentWithimage(image162), cm},
  1306  				clusterConfig: &bootstrapv1.ClusterConfiguration{
  1307  					DNS: bootstrapv1.DNS{
  1308  						ImageMeta: bootstrapv1.ImageMeta{
  1309  							ImageTag: "1.8.0",
  1310  						},
  1311  					},
  1312  				},
  1313  				kubernetesVersion: semver.MustParse("1.25.0"),
  1314  				expectedInfo: coreDNSInfo{
  1315  					CurrentMajorMinorPatch: "1.6.2",
  1316  					FromImageTag:           "1.6.2",
  1317  					TargetMajorMinorPatch:  "1.8.0",
  1318  					FromImage:              image162,
  1319  					ToImage:                "registry.k8s.io/coredns/coredns:1.8.0",
  1320  					ToImageTag:             "1.8.0",
  1321  				},
  1322  			},
  1323  			{
  1324  				name:          "returns error if unable to find coredns config map",
  1325  				objs:          []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162)},
  1326  				clusterConfig: clusterConfig,
  1327  				expectErr:     true,
  1328  			},
  1329  			{
  1330  				name:          "returns error if unable to find coredns deployment",
  1331  				objs:          []client.Object{cm},
  1332  				clusterConfig: clusterConfig,
  1333  				expectErr:     true,
  1334  			},
  1335  			{
  1336  				name:          "returns error if coredns deployment doesn't have coredns container",
  1337  				objs:          []client.Object{emptyDepl, cm},
  1338  				clusterConfig: clusterConfig,
  1339  				expectErr:     true,
  1340  			},
  1341  			{
  1342  				name:          "returns error if unable to find coredns corefile",
  1343  				objs:          []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), emptycm},
  1344  				clusterConfig: clusterConfig,
  1345  				expectErr:     true,
  1346  			},
  1347  			{
  1348  				name:          "returns error if unable to parse the container image",
  1349  				objs:          []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/asd:1123/asd:coredns:1.6.1"), cm},
  1350  				clusterConfig: clusterConfig,
  1351  				expectErr:     true,
  1352  			},
  1353  			{
  1354  				name:          "returns error if container image has not tag",
  1355  				objs:          []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/coredns"), cm},
  1356  				clusterConfig: clusterConfig,
  1357  				expectErr:     true,
  1358  			},
  1359  			{
  1360  				name:          "returns error if unable to semver parse container image",
  1361  				objs:          []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/coredns:v1X6.2"), cm},
  1362  				clusterConfig: clusterConfig,
  1363  				expectErr:     true,
  1364  			},
  1365  			{
  1366  				name:          "returns error if unable to semver parse dns image tag",
  1367  				objs:          []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm},
  1368  				clusterConfig: badImgTagDNS,
  1369  				expectErr:     true,
  1370  			},
  1371  		}
  1372  		for i := range tests {
  1373  			tt := tests[i]
  1374  			t.Run(tt.name, func(t *testing.T) {
  1375  				g := NewWithT(t)
  1376  				fakeClient := fake.NewClientBuilder().WithObjects(tt.objs...).Build()
  1377  				w := &Workload{
  1378  					Client: fakeClient,
  1379  				}
  1380  
  1381  				var actualDepl *appsv1.Deployment
  1382  				for _, o := range tt.objs {
  1383  					if d, ok := o.(*appsv1.Deployment); ok {
  1384  						actualDepl = d
  1385  						break
  1386  					}
  1387  				}
  1388  
  1389  				actualInfo, err := w.getCoreDNSInfo(ctx, tt.clusterConfig, tt.kubernetesVersion)
  1390  				if tt.expectErr {
  1391  					g.Expect(err).To(HaveOccurred())
  1392  					return
  1393  				}
  1394  				g.Expect(err).ToNot(HaveOccurred())
  1395  				tt.expectedInfo.Corefile = expectedCorefile
  1396  				tt.expectedInfo.Deployment = actualDepl
  1397  
  1398  				g.Expect(actualInfo).To(BeComparableTo(&tt.expectedInfo))
  1399  			})
  1400  		}
  1401  	})
  1402  }
  1403  
  1404  func TestUpdateCoreDNSImageInfoInKubeadmConfigMap(t *testing.T) {
  1405  	tests := []struct {
  1406  		name                     string
  1407  		clusterConfigurationData string
  1408  		newDNS                   bootstrapv1.DNS
  1409  		wantClusterConfiguration string
  1410  	}{
  1411  		{
  1412  			name: "it should set the DNS image config",
  1413  			clusterConfigurationData: utilyaml.Raw(`
  1414  				apiVersion: kubeadm.k8s.io/v1beta2
  1415  				kind: ClusterConfiguration
  1416  				`),
  1417  			newDNS: bootstrapv1.DNS{
  1418  				ImageMeta: bootstrapv1.ImageMeta{
  1419  					ImageRepository: "example.com/k8s",
  1420  					ImageTag:        "v1.2.3",
  1421  				},
  1422  			},
  1423  			wantClusterConfiguration: utilyaml.Raw(`
  1424  				apiServer: {}
  1425  				apiVersion: kubeadm.k8s.io/v1beta2
  1426  				controllerManager: {}
  1427  				dns:
  1428  				  imageRepository: example.com/k8s
  1429  				  imageTag: v1.2.3
  1430  				etcd: {}
  1431  				kind: ClusterConfiguration
  1432  				networking: {}
  1433  				scheduler: {}
  1434  				`),
  1435  		},
  1436  	}
  1437  	for i := range tests {
  1438  		tt := tests[i]
  1439  		t.Run(tt.name, func(t *testing.T) {
  1440  			g := NewWithT(t)
  1441  			fakeClient := fake.NewClientBuilder().WithObjects(&corev1.ConfigMap{
  1442  				ObjectMeta: metav1.ObjectMeta{
  1443  					Name:      kubeadmConfigKey,
  1444  					Namespace: metav1.NamespaceSystem,
  1445  				},
  1446  				Data: map[string]string{
  1447  					clusterConfigurationKey: tt.clusterConfigurationData,
  1448  				},
  1449  			}).Build()
  1450  
  1451  			w := &Workload{
  1452  				Client: fakeClient,
  1453  			}
  1454  			err := w.UpdateClusterConfiguration(ctx, semver.MustParse("1.19.1"), w.updateCoreDNSImageInfoInKubeadmConfigMap(&tt.newDNS))
  1455  			g.Expect(err).ToNot(HaveOccurred())
  1456  
  1457  			var actualConfig corev1.ConfigMap
  1458  			g.Expect(w.Client.Get(
  1459  				ctx,
  1460  				client.ObjectKey{Name: kubeadmConfigKey, Namespace: metav1.NamespaceSystem},
  1461  				&actualConfig,
  1462  			)).To(Succeed())
  1463  			g.Expect(actualConfig.Data[clusterConfigurationKey]).Should(Equal(tt.wantClusterConfiguration), cmp.Diff(tt.wantClusterConfiguration, actualConfig.Data[clusterConfigurationKey]))
  1464  		})
  1465  	}
  1466  }
  1467  
  1468  func TestUpdateCoreDNSDeployment(t *testing.T) {
  1469  	depl := &appsv1.Deployment{
  1470  		ObjectMeta: metav1.ObjectMeta{
  1471  			Name:      coreDNSKey,
  1472  			Namespace: metav1.NamespaceSystem,
  1473  		},
  1474  		Spec: appsv1.DeploymentSpec{
  1475  			Template: corev1.PodTemplateSpec{
  1476  				ObjectMeta: metav1.ObjectMeta{
  1477  					Name: coreDNSKey,
  1478  				},
  1479  				Spec: corev1.PodSpec{
  1480  					Containers: []corev1.Container{{
  1481  						Name:  coreDNSKey,
  1482  						Image: "k8s.gcr.io/coredns:1.6.2",
  1483  						Args:  []string{"-conf", "/etc/coredns/Corefile"},
  1484  					}},
  1485  					Volumes: []corev1.Volume{{
  1486  						Name: "config-volume",
  1487  						VolumeSource: corev1.VolumeSource{
  1488  							ConfigMap: &corev1.ConfigMapVolumeSource{
  1489  								LocalObjectReference: corev1.LocalObjectReference{
  1490  									Name: coreDNSKey,
  1491  								},
  1492  								Items: []corev1.KeyToPath{{
  1493  									Key:  corefileBackupKey,
  1494  									Path: corefileKey,
  1495  								}},
  1496  							},
  1497  						},
  1498  					}},
  1499  				},
  1500  			},
  1501  		},
  1502  	}
  1503  
  1504  	tests := []struct {
  1505  		name      string
  1506  		objs      []client.Object
  1507  		info      *coreDNSInfo
  1508  		expectErr bool
  1509  	}{
  1510  		{
  1511  			name: "patches coredns deployment successfully",
  1512  			objs: []client.Object{depl},
  1513  			info: &coreDNSInfo{
  1514  				Deployment:             depl.DeepCopy(),
  1515  				Corefile:               "updated-core-file",
  1516  				FromImage:              "k8s.gcr.io/coredns:1.6.2",
  1517  				ToImage:                "myrepo/mycoredns:1.7.2-foobar.1",
  1518  				CurrentMajorMinorPatch: "1.6.2",
  1519  				TargetMajorMinorPatch:  "1.7.2",
  1520  			},
  1521  		},
  1522  		{
  1523  			name: "returns error if patch fails",
  1524  			objs: []client.Object{},
  1525  			info: &coreDNSInfo{
  1526  				Deployment:             depl.DeepCopy(),
  1527  				Corefile:               "updated-core-file",
  1528  				FromImage:              "k8s.gcr.io/coredns:1.6.2",
  1529  				ToImage:                "myrepo/mycoredns:1.7.2-foobar.1",
  1530  				CurrentMajorMinorPatch: "1.6.2",
  1531  				TargetMajorMinorPatch:  "1.7.2",
  1532  			},
  1533  			expectErr: true,
  1534  		},
  1535  		{
  1536  			name: "deployment is nil for some reason",
  1537  			info: &coreDNSInfo{
  1538  				Deployment:             nil,
  1539  				Corefile:               "updated-core-file",
  1540  				FromImage:              "k8s.gcr.io/coredns:1.6.2",
  1541  				ToImage:                "myrepo/mycoredns:1.7.2-foobar.1",
  1542  				CurrentMajorMinorPatch: "1.6.2",
  1543  				TargetMajorMinorPatch:  "1.7.2",
  1544  			},
  1545  			expectErr: true,
  1546  		},
  1547  	}
  1548  
  1549  	for _, tt := range tests {
  1550  		t.Run(tt.name, func(t *testing.T) {
  1551  			g := NewWithT(t)
  1552  			fakeClient := fake.NewClientBuilder().WithObjects(tt.objs...).Build()
  1553  
  1554  			w := &Workload{
  1555  				Client: fakeClient,
  1556  			}
  1557  
  1558  			err := w.updateCoreDNSDeployment(ctx, tt.info, semver.Version{Major: 1, Minor: 26, Patch: 0})
  1559  			if tt.expectErr {
  1560  				g.Expect(err).To(HaveOccurred())
  1561  				return
  1562  			}
  1563  			g.Expect(err).ToNot(HaveOccurred())
  1564  
  1565  			expectedVolume := corev1.Volume{
  1566  				Name: "config-volume",
  1567  				VolumeSource: corev1.VolumeSource{
  1568  					ConfigMap: &corev1.ConfigMapVolumeSource{
  1569  						LocalObjectReference: corev1.LocalObjectReference{
  1570  							Name: coreDNSKey,
  1571  						},
  1572  						Items: []corev1.KeyToPath{{
  1573  							Key:  corefileKey,
  1574  							Path: corefileKey,
  1575  						}},
  1576  					},
  1577  				},
  1578  			}
  1579  
  1580  			var actualDeployment appsv1.Deployment
  1581  			g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &actualDeployment)).To(Succeed())
  1582  			// ensure the image is updated and the volumes point to the corefile
  1583  			g.Expect(actualDeployment.Spec.Template.Spec.Containers[0].Image).To(Equal(tt.info.ToImage))
  1584  			g.Expect(actualDeployment.Spec.Template.Spec.Volumes).To(ConsistOf(expectedVolume))
  1585  		})
  1586  	}
  1587  }
  1588  
  1589  func TestPatchCoreDNSDeploymentTolerations(t *testing.T) {
  1590  	oldControlPlaneToleration := corev1.Toleration{
  1591  		Key:    oldControlPlaneTaint,
  1592  		Effect: corev1.TaintEffectNoSchedule,
  1593  	}
  1594  	controlPlaneToleration := corev1.Toleration{
  1595  		Key:    controlPlaneTaint,
  1596  		Effect: corev1.TaintEffectNoSchedule,
  1597  	}
  1598  
  1599  	tests := []struct {
  1600  		name                string
  1601  		currentTolerations  []corev1.Toleration
  1602  		kubernetesVersion   semver.Version
  1603  		expectedTolerations []corev1.Toleration
  1604  	}{
  1605  		{
  1606  			name:               "adds both tolerations for Kubernetes v1.25",
  1607  			currentTolerations: []corev1.Toleration{},
  1608  			kubernetesVersion:  semver.Version{Major: 1, Minor: 25, Patch: 0},
  1609  			expectedTolerations: []corev1.Toleration{
  1610  				controlPlaneToleration,
  1611  				oldControlPlaneToleration,
  1612  			},
  1613  		},
  1614  		{
  1615  			name:               "adds only new toleration for Kubernetes v1.26",
  1616  			currentTolerations: []corev1.Toleration{},
  1617  			kubernetesVersion:  semver.Version{Major: 1, Minor: 26, Patch: 0},
  1618  			expectedTolerations: []corev1.Toleration{
  1619  				controlPlaneToleration,
  1620  			},
  1621  		},
  1622  		{
  1623  			name: "adds both tolerations for Kubernetes v1.25 and preserves additional tolerations",
  1624  			currentTolerations: []corev1.Toleration{
  1625  				{
  1626  					Key:    "my-special.custom/taint",
  1627  					Effect: corev1.TaintEffectNoExecute,
  1628  					Value:  "aValue",
  1629  				},
  1630  			},
  1631  			kubernetesVersion: semver.Version{Major: 1, Minor: 25, Patch: 0},
  1632  			expectedTolerations: []corev1.Toleration{
  1633  				controlPlaneToleration,
  1634  				oldControlPlaneToleration,
  1635  				{
  1636  					Key:    "my-special.custom/taint",
  1637  					Effect: corev1.TaintEffectNoExecute,
  1638  					Value:  "aValue",
  1639  				},
  1640  			},
  1641  		},
  1642  		{
  1643  			name: "ensures only new toleration is set for Kubernetes v1.26, drops old toleration and preserves additional tolerations",
  1644  			currentTolerations: []corev1.Toleration{
  1645  				oldControlPlaneToleration,
  1646  				{
  1647  					Key:    "my-special.custom/taint",
  1648  					Effect: corev1.TaintEffectNoExecute,
  1649  					Value:  "aValue",
  1650  				},
  1651  			},
  1652  			kubernetesVersion: semver.Version{Major: 1, Minor: 25, Patch: 0},
  1653  			expectedTolerations: []corev1.Toleration{
  1654  				controlPlaneToleration,
  1655  				oldControlPlaneToleration,
  1656  				{
  1657  					Key:    "my-special.custom/taint",
  1658  					Effect: corev1.TaintEffectNoExecute,
  1659  					Value:  "aValue",
  1660  				},
  1661  			},
  1662  		},
  1663  	}
  1664  
  1665  	for _, tt := range tests {
  1666  		t.Run(tt.name, func(t *testing.T) {
  1667  			g := NewWithT(t)
  1668  
  1669  			d := &appsv1.Deployment{
  1670  				Spec: appsv1.DeploymentSpec{
  1671  					Template: corev1.PodTemplateSpec{
  1672  						Spec: corev1.PodSpec{
  1673  							Tolerations: tt.currentTolerations,
  1674  						},
  1675  					},
  1676  				},
  1677  			}
  1678  
  1679  			patchCoreDNSDeploymentTolerations(d, tt.kubernetesVersion)
  1680  
  1681  			g.Expect(d.Spec.Template.Spec.Tolerations).To(BeComparableTo(tt.expectedTolerations))
  1682  		})
  1683  	}
  1684  }
  1685  
  1686  type fakeMigrator struct {
  1687  	migrateCalled    bool
  1688  	migrateErr       error
  1689  	migratedCorefile string
  1690  }
  1691  
  1692  func (m *fakeMigrator) Migrate(_, _, _ string, _ bool) (string, error) {
  1693  	m.migrateCalled = true
  1694  	if m.migrateErr != nil {
  1695  		return "", m.migrateErr
  1696  	}
  1697  	return m.migratedCorefile, nil
  1698  }
  1699  
  1700  func newCoreDNSInfoDeploymentWithimage(image string) *appsv1.Deployment {
  1701  	return &appsv1.Deployment{
  1702  		TypeMeta: metav1.TypeMeta{
  1703  			Kind:       "Deployment",
  1704  			APIVersion: "apps/v1",
  1705  		},
  1706  		ObjectMeta: metav1.ObjectMeta{
  1707  			Name:      coreDNSKey,
  1708  			Namespace: metav1.NamespaceSystem,
  1709  		},
  1710  		Spec: appsv1.DeploymentSpec{
  1711  			Template: corev1.PodTemplateSpec{
  1712  				ObjectMeta: metav1.ObjectMeta{
  1713  					Name: coreDNSKey,
  1714  				},
  1715  				Spec: corev1.PodSpec{
  1716  					Containers: []corev1.Container{{
  1717  						Name:  coreDNSKey,
  1718  						Image: image,
  1719  					}},
  1720  				},
  1721  			},
  1722  		},
  1723  	}
  1724  }