/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package dns import ( "bytes" "context" "fmt" "strings" "testing" apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" clientset "k8s.io/client-go/kubernetes" clientsetfake "k8s.io/client-go/kubernetes/fake" clientsetscheme "k8s.io/client-go/kubernetes/scheme" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "github.com/lithammer/dedent" ) func TestCompileManifests(t *testing.T) { replicas := int32(coreDNSReplicas) var tests = []struct { name string manifest string data interface{} }{ { name: "CoreDNSDeployment manifest", manifest: CoreDNSDeployment, data: struct { DeploymentName, Image, ControlPlaneTaintKey string Replicas *int32 }{ DeploymentName: "foo", Image: "foo", ControlPlaneTaintKey: "foo", Replicas: &replicas, }, }, { name: "CoreDNSConfigMap manifest", manifest: CoreDNSConfigMap, data: struct{ DNSDomain string }{ DNSDomain: "foo", }, }, } for _, rt := range tests { t.Run(rt.name, func(t *testing.T) { _, err := kubeadmutil.ParseTemplate(rt.manifest, rt.data) if err != nil { t.Errorf("unexpected ParseTemplate failure: %+v", err) } }) } } func TestGetDNSIP(t *testing.T) { var tests = []struct { name, svcSubnet, expectedDNSIP string isDualStack bool }{ { name: "subnet mask 12", svcSubnet: "10.96.0.0/12", expectedDNSIP: "10.96.0.10", }, { name: "subnet mask 26", svcSubnet: "10.87.116.64/26", expectedDNSIP: "10.87.116.74", }, { name: "dual-stack ipv4 primary, subnet mask 26", svcSubnet: "10.87.116.64/26,fd03::/112", expectedDNSIP: "10.87.116.74", }, { name: "dual-stack ipv6 primary, subnet mask 112", svcSubnet: "fd03::/112,10.87.116.64/26", expectedDNSIP: "fd03::a", }, } for _, rt := range tests { t.Run(rt.name, func(t *testing.T) { dnsIP, err := kubeadmconstants.GetDNSIP(rt.svcSubnet) if err != nil { t.Fatalf("couldn't get dnsIP : %v", err) } actualDNSIP := dnsIP.String() if actualDNSIP != rt.expectedDNSIP { t.Errorf( "failed GetDNSIP\n\texpected: %s\n\t actual: %s", rt.expectedDNSIP, actualDNSIP, ) } }) } } func TestDeploymentsHaveSystemClusterCriticalPriorityClassName(t *testing.T) { replicas := int32(coreDNSReplicas) testCases := []struct { name string manifest string data interface{} }{ { name: "CoreDNSDeployment", manifest: CoreDNSDeployment, data: struct { DeploymentName, Image, ControlPlaneTaintKey, CoreDNSConfigMapName string Replicas *int32 }{ DeploymentName: "foo", Image: "foo", ControlPlaneTaintKey: "foo", CoreDNSConfigMapName: "foo", Replicas: &replicas, }, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { deploymentBytes, _ := kubeadmutil.ParseTemplate(testCase.manifest, testCase.data) deployment := &apps.Deployment{} if err := runtime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), deploymentBytes, deployment); err != nil { t.Errorf("unexpected error: %v", err) } if deployment.Spec.Template.Spec.PriorityClassName != "system-cluster-critical" { t.Errorf("expected to see system-cluster-critical priority class name. Got %q instead", deployment.Spec.Template.Spec.PriorityClassName) } }) } } func TestCreateCoreDNSAddon(t *testing.T) { tests := []struct { name string initialCorefileData string expectedCorefileData string coreDNSVersion string }{ { name: "Empty Corefile", initialCorefileData: "", expectedCorefileData: `.:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } `, coreDNSVersion: "1.6.7", }, { name: "Default Corefile", initialCorefileData: `.:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance } `, expectedCorefileData: `.:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } `, coreDNSVersion: "1.6.7", }, { name: "Modified Corefile with only newdefaults needed", initialCorefileData: `.:53 { errors log health ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance } `, expectedCorefileData: `.:53 { errors log health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } `, coreDNSVersion: "1.6.2", }, { name: "Default Corefile with rearranged plugins", initialCorefileData: `.:53 { errors cache 30 prometheus :9153 forward . /etc/resolv.conf loop reload loadbalance kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure upstream fallthrough in-addr.arpa ip6.arpa ttl 30 } health } `, expectedCorefileData: `.:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } `, coreDNSVersion: "1.3.1", }, { name: "Remove Deprecated options", initialCorefileData: `.:53 { errors logs health kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure upstream fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance }`, expectedCorefileData: `.:53 { errors logs health { lameduck 5s } kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance ready } `, coreDNSVersion: "1.3.1", }, { name: "Update proxy plugin to forward plugin", initialCorefileData: `.:53 { errors health kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure upstream fallthrough in-addr.arpa ip6.arpa } prometheus :9153 proxy . /etc/resolv.conf k8s_external example.com cache 30 loop reload loadbalance }`, expectedCorefileData: `.:53 { errors health { lameduck 5s } kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } k8s_external example.com cache 30 loop reload loadbalance ready } `, coreDNSVersion: "1.3.1", }, { name: "Modified Corefile with no migration required", initialCorefileData: `consul { errors forward . 10.10.96.16:8600 10.10.96.17:8600 10.10.96.18:8600 { max_concurrent 1000 } loadbalance cache 5 reload } domain.int { errors forward . 10.10.0.140 10.10.0.240 10.10.51.40 { max_concurrent 1000 } loadbalance cache 3600 reload } .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . /etc/resolv.conf { prefer_udp max_concurrent 1000 } cache 30 loop reload loadbalance } `, expectedCorefileData: `consul { errors forward . 10.10.96.16:8600 10.10.96.17:8600 10.10.96.18:8600 { max_concurrent 1000 } loadbalance cache 5 reload } domain.int { errors forward . 10.10.0.140 10.10.0.240 10.10.51.40 { max_concurrent 1000 } loadbalance cache 3600 reload } .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . /etc/resolv.conf { prefer_udp max_concurrent 1000 } cache 30 loop reload loadbalance } `, coreDNSVersion: "1.6.7", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { client := createClientAndCoreDNSManifest(t, tc.initialCorefileData, tc.coreDNSVersion) configMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, UpstreamNameserver, StubDomain string }{ DNSDomain: "cluster.local", UpstreamNameserver: "/etc/resolv.conf", StubDomain: "", }) if err != nil { t.Errorf("unexpected ParseTemplate failure: %+v", err) } err = createCoreDNSAddon(nil, nil, configMapBytes, client) if err != nil { t.Fatalf("error creating the CoreDNS Addon: %v", err) } migratedConfigMap, _ := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(context.TODO(), kubeadmconstants.CoreDNSConfigMap, metav1.GetOptions{}) if !strings.EqualFold(migratedConfigMap.Data["Corefile"], tc.expectedCorefileData) { t.Fatalf("expected to get %v, but got %v", tc.expectedCorefileData, migratedConfigMap.Data["Corefile"]) } }) } } func createClientAndCoreDNSManifest(t *testing.T, corefile, coreDNSVersion string) *clientsetfake.Clientset { client := clientsetfake.NewSimpleClientset() _, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Create(context.TODO(), &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: kubeadmconstants.CoreDNSConfigMap, Namespace: metav1.NamespaceSystem, }, Data: map[string]string{ "Corefile": corefile, }, }, metav1.CreateOptions{}) if err != nil { t.Fatalf("error creating ConfigMap: %v", err) } _, err = client.AppsV1().Deployments(metav1.NamespaceSystem).Create(context.TODO(), &apps.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: kubeadmconstants.CoreDNSConfigMap, Namespace: metav1.NamespaceSystem, Labels: map[string]string{ "k8s-app": "kube-dns", }, }, Spec: apps.DeploymentSpec{ Template: v1.PodTemplateSpec{ Spec: v1.PodSpec{ Containers: []v1.Container{ { Image: "test:" + coreDNSVersion, }, }, }, }, }, }, metav1.CreateOptions{}) if err != nil { t.Fatalf("error creating deployment: %v", err) } return client } func TestDeployedDNSReplicas(t *testing.T) { tests := []struct { name string deploymentSize int want int32 wantErr bool }{ { name: "one coredns addon deployment", deploymentSize: 1, want: 2, wantErr: false, }, { name: "no coredns addon deployment", deploymentSize: 0, want: 5, wantErr: false, }, { name: "multiple coredns addon deployments", deploymentSize: 3, want: 5, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { client := newMockClientForTest(t, 2, tt.deploymentSize, "") got, err := deployedDNSReplicas(client, 5) if (err != nil) != tt.wantErr { t.Errorf("deployedDNSReplicas() error = %v, wantErr %v", err, tt.wantErr) return } if *got != tt.want { t.Errorf("deployedDNSReplicas() = %v, want %v", *got, tt.want) } }) } } func TestCoreDNSAddon(t *testing.T) { type args struct { cfg *kubeadmapi.ClusterConfiguration client clientset.Interface printManifest bool } tests := []struct { name string args args wantOut string wantErr bool }{ { name: "cfg is empty", args: args{ cfg: &kubeadmapi.ClusterConfiguration{}, client: newMockClientForTest(t, 2, 1, ""), printManifest: false, }, wantOut: "", wantErr: true, }, { name: "cfg is valid and not print Manifest", args: args{ cfg: &kubeadmapi.ClusterConfiguration{ DNS: kubeadmapi.DNS{ ImageMeta: kubeadmapi.ImageMeta{ ImageRepository: "daocloud.io", }, }, Networking: kubeadmapi.Networking{ ServiceSubnet: "10.0.0.0/16", }, }, client: newMockClientForTest(t, 2, 1, ""), printManifest: false, }, wantOut: "[addons] Applied essential addon: CoreDNS\n", wantErr: false, }, { name: "cfg is valid and print Manifest", args: args{ cfg: &kubeadmapi.ClusterConfiguration{ DNS: kubeadmapi.DNS{ ImageMeta: kubeadmapi.ImageMeta{ ImageRepository: "daocloud.io", }, }, Networking: kubeadmapi.Networking{ ServiceSubnet: "10.0.0.0/16", }, }, client: newMockClientForTest(t, 2, 1, ""), printManifest: true, }, wantOut: dedent.Dedent(`--- apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system labels: k8s-app: kube-dns spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 selector: matchLabels: k8s-app: kube-dns template: metadata: labels: k8s-app: kube-dns spec: priorityClassName: system-cluster-critical serviceAccountName: coredns affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: k8s-app operator: In values: ["kube-dns"] topologyKey: kubernetes.io/hostname tolerations: - key: CriticalAddonsOnly operator: Exists - key: node-role.kubernetes.io/control-plane effect: NoSchedule nodeSelector: kubernetes.io/os: linux containers: - name: coredns image: daocloud.io/coredns:v1.11.1 imagePullPolicy: IfNotPresent resources: limits: memory: 170Mi requests: cpu: 100m memory: 70Mi args: [ "-conf", "/etc/coredns/Corefile" ] volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics protocol: TCP livenessProbe: httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 60 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: httpGet: path: /ready port: 8181 scheme: HTTP securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - ALL readOnlyRootFilesystem: true dnsPolicy: Default volumes: - name: config-volume configMap: name: coredns items: - key: Corefile path: Corefile --- apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } --- apiVersion: v1 kind: Service metadata: labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" kubernetes.io/name: "CoreDNS" name: kube-dns namespace: kube-system annotations: prometheus.io/port: "9153" prometheus.io/scrape: "true" # Without this resourceVersion value, an update of the Service between versions will yield: # Service "kube-dns" is invalid: metadata.resourceVersion: Invalid value: "": must be specified for an update resourceVersion: "0" spec: clusterIP: 10.0.0.10 ports: - name: dns port: 53 protocol: UDP targetPort: 53 - name: dns-tcp port: 53 protocol: TCP targetPort: 53 - name: metrics port: 9153 protocol: TCP targetPort: 9153 selector: k8s-app: kube-dns --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: system:coredns rules: - apiGroups: - "" resources: - endpoints - services - pods - namespaces verbs: - list - watch - apiGroups: - discovery.k8s.io resources: - endpointslices verbs: - list - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: system:coredns roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:coredns subjects: - kind: ServiceAccount name: coredns namespace: kube-system --- apiVersion: v1 kind: ServiceAccount metadata: name: coredns namespace: kube-system `), wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { out := &bytes.Buffer{} var replicas int32 = 3 if err := coreDNSAddon(tt.args.cfg, tt.args.client, &replicas, out, tt.args.printManifest); (err != nil) != tt.wantErr { t.Errorf("coreDNSAddon() error = %v, wantErr %v", err, tt.wantErr) return } if gotOut := out.String(); gotOut != tt.wantOut { t.Errorf("Actual output of coreDNSAddon() does not match expected.\nActual: %v\nExpected: %v\n", gotOut, tt.wantOut) } }) } } func TestEnsureDNSAddon(t *testing.T) { type args struct { cfg *kubeadmapi.ClusterConfiguration client clientset.Interface printManifest bool } tests := []struct { name string args args wantOut string wantErr bool }{ { name: "not print Manifest", args: args{ cfg: &kubeadmapi.ClusterConfiguration{ DNS: kubeadmapi.DNS{ ImageMeta: kubeadmapi.ImageMeta{ ImageRepository: "daocloud.io", }, }, Networking: kubeadmapi.Networking{ ServiceSubnet: "10.0.0.0/16", }, }, client: newMockClientForTest(t, 0, 1, ""), printManifest: false, }, wantOut: "[addons] Applied essential addon: CoreDNS\n", }, { name: "not print Manifest", args: args{ cfg: &kubeadmapi.ClusterConfiguration{ DNS: kubeadmapi.DNS{ ImageMeta: kubeadmapi.ImageMeta{ ImageRepository: "daocloud.io", }, }, Networking: kubeadmapi.Networking{ ServiceSubnet: "10.0.0.0/16", }, }, client: newMockClientForTest(t, 0, 1, ""), printManifest: true, }, wantOut: dedent.Dedent(`--- apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system labels: k8s-app: kube-dns spec: replicas: 2 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 selector: matchLabels: k8s-app: kube-dns template: metadata: labels: k8s-app: kube-dns spec: priorityClassName: system-cluster-critical serviceAccountName: coredns affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: k8s-app operator: In values: ["kube-dns"] topologyKey: kubernetes.io/hostname tolerations: - key: CriticalAddonsOnly operator: Exists - key: node-role.kubernetes.io/control-plane effect: NoSchedule nodeSelector: kubernetes.io/os: linux containers: - name: coredns image: daocloud.io/coredns:v1.11.1 imagePullPolicy: IfNotPresent resources: limits: memory: 170Mi requests: cpu: 100m memory: 70Mi args: [ "-conf", "/etc/coredns/Corefile" ] volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics protocol: TCP livenessProbe: httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 60 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: httpGet: path: /ready port: 8181 scheme: HTTP securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - ALL readOnlyRootFilesystem: true dnsPolicy: Default volumes: - name: config-volume configMap: name: coredns items: - key: Corefile path: Corefile --- apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } --- apiVersion: v1 kind: Service metadata: labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" kubernetes.io/name: "CoreDNS" name: kube-dns namespace: kube-system annotations: prometheus.io/port: "9153" prometheus.io/scrape: "true" # Without this resourceVersion value, an update of the Service between versions will yield: # Service "kube-dns" is invalid: metadata.resourceVersion: Invalid value: "": must be specified for an update resourceVersion: "0" spec: clusterIP: 10.0.0.10 ports: - name: dns port: 53 protocol: UDP targetPort: 53 - name: dns-tcp port: 53 protocol: TCP targetPort: 53 - name: metrics port: 9153 protocol: TCP targetPort: 9153 selector: k8s-app: kube-dns --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: system:coredns rules: - apiGroups: - "" resources: - endpoints - services - pods - namespaces verbs: - list - watch - apiGroups: - discovery.k8s.io resources: - endpointslices verbs: - list - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: system:coredns roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:coredns subjects: - kind: ServiceAccount name: coredns namespace: kube-system --- apiVersion: v1 kind: ServiceAccount metadata: name: coredns namespace: kube-system `), wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { out := &bytes.Buffer{} if err := EnsureDNSAddon(tt.args.cfg, tt.args.client, out, tt.args.printManifest); (err != nil) != tt.wantErr { t.Errorf("EnsureDNSAddon() error = %v, wantErr %v", err, tt.wantErr) return } if gotOut := out.String(); gotOut != tt.wantOut { t.Errorf("Actual output of EnsureDNSAddon() does not match expected.\nActual: %v\nExpected: %v\n", gotOut, tt.wantOut) } }) } } func TestCreateDNSService(t *testing.T) { coreDNSServiceBytes, _ := kubeadmutil.ParseTemplate(CoreDNSService, struct{ DNSIP string }{ DNSIP: "10.233.0.3", }) type args struct { dnsService *v1.Service serviceBytes []byte } tests := []struct { name string args args wantErr bool }{ { name: "dnsService and serviceBytes are nil", args: args{ dnsService: nil, serviceBytes: nil, }, wantErr: true, }, { name: "invalid dns", args: args{ dnsService: nil, serviceBytes: coreDNSServiceBytes, }, wantErr: true, }, { name: "serviceBytes is not valid", args: args{ dnsService: &v1.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{Name: "coredns", Labels: map[string]string{"k8s-app": "kube-dns", "kubernetes.io/name": "coredns"}, Namespace: "kube-system", }, Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ { Name: "dns", Port: 53, Protocol: v1.ProtocolUDP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, { Name: "dns-tcp", Port: 53, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, }, Selector: map[string]string{ "k8s-app": "kube-dns", }, }, }, serviceBytes: []byte{ 'f', 'o', 'o', }, }, wantErr: true, }, { name: "dnsService is valid and serviceBytes is nil", args: args{ dnsService: &v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "coredns", Labels: map[string]string{"k8s-app": "kube-dns", "kubernetes.io/name": "coredns"}, Namespace: "kube-system", }, Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ { Name: "dns", Port: 53, Protocol: v1.ProtocolUDP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, { Name: "dns-tcp", Port: 53, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, }, Selector: map[string]string{ "k8s-app": "kube-dns", }, }, }, serviceBytes: nil, }, wantErr: false, }, { name: "dnsService and serviceBytes are not nil and valid", args: args{ dnsService: &v1.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{Name: "coredns", Labels: map[string]string{"k8s-app": "kube-dns", "kubernetes.io/name": "coredns"}, Namespace: "kube-system", }, Spec: v1.ServiceSpec{ ClusterIP: "10.233.0.3", Ports: []v1.ServicePort{ { Name: "dns", Port: 53, Protocol: v1.ProtocolUDP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, }, Selector: map[string]string{ "k8s-app": "kube-dns", }, }, }, serviceBytes: coreDNSServiceBytes, }, wantErr: false, }, { name: "the namespace of dnsService is not kube-system", args: args{ dnsService: &v1.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{Name: "coredns", Labels: map[string]string{"k8s-app": "kube-dns", "kubernetes.io/name": "coredns"}, Namespace: "kube-system-test", }, Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ { Name: "dns", Port: 53, Protocol: v1.ProtocolUDP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, { Name: "dns-tcp", Port: 53, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, }, Selector: map[string]string{ "k8s-app": "kube-dns", }, }, }, serviceBytes: nil, }, wantErr: true, }, { name: "the name of dnsService is not coredns", args: args{ dnsService: &v1.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{Name: "coredns-test", Labels: map[string]string{"k8s-app": "kube-dns", "kubernetes.io/name": "coredns"}, Namespace: "kube-system", }, Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ { Name: "dns", Port: 53, Protocol: v1.ProtocolUDP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, { Name: "dns-tcp", Port: 53, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, }, Selector: map[string]string{ "k8s-app": "kube-dns", }, }, }, serviceBytes: nil, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { client := newMockClientForTest(t, 1, 1, "") if err := createDNSService(tt.args.dnsService, tt.args.serviceBytes, client); (err != nil) != tt.wantErr { t.Errorf("createDNSService() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestDeployedDNSAddon(t *testing.T) { tests := []struct { name string image string wantVersion string wantErr bool }{ { name: "default", image: "registry.k8s.io/coredns/coredns:v1.11.1", wantVersion: "v1.11.1", }, { name: "with digest", image: "registry.k8s.io/coredns/coredns:v1.11.1@sha256:a0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e", wantVersion: "v1.11.1", }, { name: "without registry", image: "coredns/coredns:coredns-s390x", wantVersion: "coredns-s390x", }, { name: "without registry and tag", image: "coredns/coredns", wantVersion: "", }, { name: "with explicit port", image: "localhost:4711/coredns/coredns:v1.11.2-pre.1", wantVersion: "v1.11.2-pre.1", }, { name: "with explicit port but without tag", image: "localhost:4711/coredns/coredns@sha256:a0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e", wantVersion: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { client := newMockClientForTest(t, 1, 1, tt.image) version, err := DeployedDNSAddon(client) if (err != nil) != tt.wantErr { t.Errorf("DeployedDNSAddon() error = %v, wantErr %v", err, tt.wantErr) } if version != tt.wantVersion { t.Errorf("DeployedDNSAddon() for image %q returned %q, want %q", tt.image, version, tt.wantVersion) } }) } } // replicas is replica of each DNS deployment // deploymentSize is the number of deployments with `k8s-app=kube-dns` label. func newMockClientForTest(t *testing.T, replicas int32, deploymentSize int, image string) *clientsetfake.Clientset { if image == "" { image = "registry.k8s.io/coredns/coredns:v1.11.1" } client := clientsetfake.NewSimpleClientset() for i := 0; i < deploymentSize; i++ { _, err := client.AppsV1().Deployments(metav1.NamespaceSystem).Create(context.TODO(), &apps.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("coredns-%d", i), Namespace: metav1.NamespaceSystem, Labels: map[string]string{ "k8s-app": "kube-dns", }, }, Spec: apps.DeploymentSpec{ Replicas: &replicas, Template: v1.PodTemplateSpec{ Spec: v1.PodSpec{ Containers: []v1.Container{{Image: image}}, }, }, }, }, metav1.CreateOptions{}) if err != nil { t.Fatalf("error creating deployment: %v", err) } } _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(context.TODO(), &v1.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{Name: "coredns", Labels: map[string]string{"k8s-app": "kube-dns", "kubernetes.io/name": "coredns"}, Namespace: "kube-system", }, Spec: v1.ServiceSpec{ ClusterIP: "10.233.0.3", Ports: []v1.ServicePort{ { Name: "dns", Port: 53, Protocol: v1.ProtocolUDP, TargetPort: intstr.IntOrString{ Type: 0, IntVal: 53, }, }, }, Selector: map[string]string{ "k8s-app": "kube-dns", }, }, }, metav1.CreateOptions{}) if err != nil { t.Fatalf("error creating service: %v", err) } return client }