/*
Copyright 2018 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 config

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/version"
	clientset "k8s.io/client-go/kubernetes"
	clientsetfake "k8s.io/client-go/kubernetes/fake"
	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
)

var k8sVersionString = kubeadmconstants.MinimumControlPlaneVersion.String()
var k8sVersion = version.MustParseGeneric(k8sVersionString)
var nodeName = "mynode"
var cfgFiles = map[string][]byte{
	"InitConfiguration_v1beta1": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: InitConfiguration
`),
	"ClusterConfiguration_v1beta1": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: ` + k8sVersionString + `
`),
	"ClusterStatus_v1beta1": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterStatus
apiEndpoints: 
  ` + nodeName + `: 
    advertiseAddress: 1.2.3.4
    bindPort: 1234
`),
	"ClusterStatus_v1beta1_Without_APIEndpoints": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterStatus
`),
	"InitConfiguration_v1beta2": []byte(`
apiVersion: kubeadm.k8s.io/v1beta2
kind: InitConfiguration
`),
	"ClusterConfiguration_v1beta2": []byte(`
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: ` + k8sVersionString + `
`),
	"ClusterStatus_v1beta2": []byte(`
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterStatus
apiEndpoints: 
  ` + nodeName + `: 
    advertiseAddress: 1.2.3.4
    bindPort: 1234
`),
	"ClusterStatus_v1beta2_Without_APIEndpoints": []byte(`
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterStatus
`),
	"Kube-proxy_componentconfig": []byte(`
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
`),
	"Kubelet_componentconfig": []byte(`
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
}

var kubeletConfFiles = map[string][]byte{
	"withoutX509Cert": []byte(`
apiVersion: v1
clusters:
- cluster:
    server: https://10.0.2.15:6443
    name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:node:mynode
  name: system:node:mynode@kubernetes
current-context: system:node:mynode@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:mynode
  user:
`),
	"configWithEmbeddedCert": []byte(`
apiVersion: v1
clusters:
- cluster:
    server: https://10.0.2.15:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:node:mynode
  name: system:node:mynode@kubernetes
current-context: system:node:mynode@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:mynode
  user:
      client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQWl3VURhYk5vZ1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBNU1ERXhOVE14TWpaYUZ3MHhPVEE1TURFeE5qQXhOVGxhTURReApGVEFUQmdOVkJBb1RESE41YzNSbGJUcHViMlJsY3pFYk1Ca0dBMVVFQXhNU2MzbHpkR1Z0T201dlpHVTZiWGx1CmIyUmxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWs2UXUzeStyNEZYUzZ4VkoKWU1vNE9kSkt3R1d1MHY4TEJIUnhhOUhvVHo1RXZLQnB1OVJoemt5dStUaFczb0xta2ZTRmNJcitHa0M5MW0zOApFelRmVE5JY2dsL0V5YkpmR1QvdGdUazZYd1kxY1UrUUdmSEFNNTBCVzFXTFVHc25CSllJZjA5eENnZTVoTkxLCnREeUJOWWNQZzg1bUJpOU9CNFJ2UlgyQVFRMjJwZ0xrQUpJWklOU0FEdUFrODN2b051SXM2YVY2bHBkR2Vva3YKdDlpTFdNR3p3a3ZTZUZQTlNGeWZ3Q055eENjb1FDQUNtSnJRS3NoeUE2bWNzdVhORWVXdlRQdVVFSWZPVFl4dwpxdkszRVBOK0xUYlA2anhUMWtTcFJUOSt4Z29uSlFhU0RsbUNBd20zRGJkSVppWUt3R2ppMkxKL0kvYWc0cTlzCjNLb0J2UUlEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLcVVrU21jdW85OG5EK015b005VFdEV0pyTndySXpQTUNqRQpCSkdyREhVaHIwcEZlRjc0RHViODNzRXlaNjFxNUVQd2Y0enNLSzdzdDRUTzZhcE9pZWJYVmN3dnZoa09HQ2dFCmFVdGNOMjFhUGxtU0tOd0c4ai8yK3ZhbU80bGplK1NnZzRUUVB0eDZWejh5VXN2RFhxSUZycjNNd1gzSDA1RW4KWXAzN05JYkhKbGxHUW5LVHA5aTg5aXF4WXVhSERqZldiVHlEY3B5NldNVjdVaFYvY1plc3lGL0NBamNHd1V6YgowRlo5bW5tMnFONlBGWHZ4RmdMSGFWZzN2SVVCbkNmVVVyY1BDNE94VFNPK21aUmUxazh3eUFpVWovSk0rZllvCkcrMi9sbThUYVZqb1U3Rmk1S2E1RzVIWTJHTGFSN1ArSXhZY3JNSENsNjJZN1JxY3JuYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
`),
	"configWithLinkedCert": []byte(`
apiVersion: v1
clusters:
- cluster:
    server: https://10.0.2.15:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:node:mynode
  name: system:node:mynode@kubernetes
current-context: system:node:mynode@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:mynode
  user:
      client-certificate: kubelet.pem
`),
}

var pemFiles = map[string][]byte{
	"mynode.pem": []byte(`
-----BEGIN CERTIFICATE-----
MIIC8jCCAdqgAwIBAgIIAiwUDabNogQwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0xODA5MDExNTMxMjZaFw0xOTA5MDExNjAxNTlaMDQx
FTATBgNVBAoTDHN5c3RlbTpub2RlczEbMBkGA1UEAxMSc3lzdGVtOm5vZGU6bXlu
b2RlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6Qu3y+r4FXS6xVJ
YMo4OdJKwGWu0v8LBHRxa9HoTz5EvKBpu9Rhzkyu+ThW3oLmkfSFcIr+GkC91m38
EzTfTNIcgl/EybJfGT/tgTk6XwY1cU+QGfHAM50BW1WLUGsnBJYIf09xCge5hNLK
tDyBNYcPg85mBi9OB4RvRX2AQQ22pgLkAJIZINSADuAk83voNuIs6aV6lpdGeokv
t9iLWMGzwkvSeFPNSFyfwCNyxCcoQCACmJrQKshyA6mcsuXNEeWvTPuUEIfOTYxw
qvK3EPN+LTbP6jxT1kSpRT9+xgonJQaSDlmCAwm3DbdIZiYKwGji2LJ/I/ag4q9s
3KoBvQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
AwIwDQYJKoZIhvcNAQELBQADggEBAKqUkSmcuo98nD+MyoM9TWDWJrNwrIzPMCjE
BJGrDHUhr0pFeF74Dub83sEyZ61q5EPwf4zsKK7st4TO6apOiebXVcwvvhkOGCgE
aUtcN21aPlmSKNwG8j/2+vamO4lje+Sgg4TQPtx6Vz8yUsvDXqIFrr3MwX3H05En
Yp37NIbHJllGQnKTp9i89iqxYuaHDjfWbTyDcpy6WMV7UhV/cZesyF/CAjcGwUzb
0FZ9mnm2qN6PFXvxFgLHaVg3vIUBnCfUUrcPC4OxTSO+mZRe1k8wyAiUj/JM+fYo
G+2/lm8TaVjoU7Fi5Ka5G5HY2GLaR7P+IxYcrMHCl62Y7Rqcrnc=
-----END CERTIFICATE-----
`),
}

func TestGetNodeNameFromKubeletConfig(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatalf("Couldn't create tmpdir")
	}
	defer os.RemoveAll(tmpdir)

	var tests = []struct {
		name              string
		kubeconfigContent []byte
		pemContent        []byte
		expectedError     bool
	}{
		{
			name:              "valid - with embedded cert",
			kubeconfigContent: kubeletConfFiles["configWithEmbeddedCert"],
		},
		{
			name:              "invalid - linked cert missing",
			kubeconfigContent: kubeletConfFiles["configWithLinkedCert"],
			expectedError:     true,
		},
		{
			name:              "valid - with linked cert",
			kubeconfigContent: kubeletConfFiles["configWithLinkedCert"],
			pemContent:        pemFiles["mynode.pem"],
		},
		{
			name:              "invalid - without embedded or linked X509Cert",
			kubeconfigContent: kubeletConfFiles["withoutX509Cert"],
			expectedError:     true,
		},
	}

	for _, rt := range tests {
		t.Run(rt.name, func(t2 *testing.T) {
			if len(rt.pemContent) > 0 {
				pemPath := filepath.Join(tmpdir, "kubelet.pem")
				err := ioutil.WriteFile(pemPath, rt.pemContent, 0644)
				if err != nil {
					t.Errorf("Couldn't create pem file: %v", err)
					return
				}
				rt.kubeconfigContent = []byte(strings.Replace(string(rt.kubeconfigContent), "kubelet.pem", pemPath, -1))
			}

			kubeconfigPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
			err := ioutil.WriteFile(kubeconfigPath, rt.kubeconfigContent, 0644)
			if err != nil {
				t.Errorf("Couldn't create kubeconfig: %v", err)
				return
			}

			name, err := getNodeNameFromKubeletConfig(tmpdir)
			if rt.expectedError != (err != nil) {
				t.Errorf("unexpected return err from getNodeRegistration: %v", err)
				return
			}
			if rt.expectedError {
				return
			}

			if name != nodeName {
				t.Errorf("invalid name")
			}
		})
	}
}

func TestGetNodeRegistration(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatalf("Couldn't create tmpdir")
	}
	defer os.RemoveAll(tmpdir)

	var tests = []struct {
		name          string
		fileContents  []byte
		node          *v1.Node
		expectedError bool
	}{
		{
			name:          "invalid - no kubelet.conf",
			expectedError: true,
		},
		{
			name:         "valid",
			fileContents: kubeletConfFiles["configWithEmbeddedCert"],
			node: &v1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: nodeName,
					Annotations: map[string]string{
						kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
					},
				},
				Spec: v1.NodeSpec{
					Taints: []v1.Taint{kubeadmconstants.ControlPlaneTaint},
				},
			},
		},
		{
			name:          "invalid - no node",
			fileContents:  kubeletConfFiles["configWithEmbeddedCert"],
			expectedError: true,
		},
	}

	for _, rt := range tests {
		t.Run(rt.name, func(t2 *testing.T) {
			cfgPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
			if len(rt.fileContents) > 0 {
				err := ioutil.WriteFile(cfgPath, rt.fileContents, 0644)
				if err != nil {
					t.Errorf("Couldn't create file")
					return
				}
			}

			client := clientsetfake.NewSimpleClientset()

			if rt.node != nil {
				_, err := client.CoreV1().Nodes().Create(rt.node)
				if err != nil {
					t.Errorf("couldn't create Node")
					return
				}
			}

			cfg := &kubeadmapi.InitConfiguration{}
			err = getNodeRegistration(tmpdir, client, &cfg.NodeRegistration)
			if rt.expectedError != (err != nil) {
				t.Errorf("unexpected return err from getNodeRegistration: %v", err)
				return
			}
			if rt.expectedError {
				return
			}

			if cfg.NodeRegistration.Name != nodeName {
				t.Errorf("invalid cfg.NodeRegistration.Name")
			}
			if cfg.NodeRegistration.CRISocket != "myCRIsocket" {
				t.Errorf("invalid cfg.NodeRegistration.CRISocket")
			}
			if len(cfg.NodeRegistration.Taints) != 1 {
				t.Errorf("invalid cfg.NodeRegistration.Taints")
			}
		})
	}
}

func TestGetAPIEndpoint(t *testing.T) {
	var tests = []struct {
		name          string
		configMap     fakeConfigMap
		expectedError bool
	}{
		{
			name: "valid v1beta1",
			configMap: fakeConfigMap{
				name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
				data: map[string]string{
					kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta1"]),
				},
			},
		},
		{
			name: "invalid v1beta1 - No ClusterStatus in kubeadm-config ConfigMap",
			configMap: fakeConfigMap{
				name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
				data: map[string]string{},
			},
			expectedError: true,
		},
		{
			name: "invalid v1beta1 - ClusterStatus without APIEndopoints",
			configMap: fakeConfigMap{
				name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
				data: map[string]string{
					kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta1_Without_APIEndpoints"]),
				},
			},
			expectedError: true,
		},
		{
			name: "valid v1beta2",
			configMap: fakeConfigMap{
				name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
				data: map[string]string{
					kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta2"]),
				},
			},
		},
		{
			name: "invalid v1beta2 - No ClusterStatus in kubeadm-config ConfigMap",
			configMap: fakeConfigMap{
				name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
				data: map[string]string{},
			},
			expectedError: true,
		},
		{
			name: "invalid v1beta2 - ClusterStatus without APIEndopoints",
			configMap: fakeConfigMap{
				name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
				data: map[string]string{
					kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta2_Without_APIEndpoints"]),
				},
			},
			expectedError: true,
		},
	}

	for _, rt := range tests {
		t.Run(rt.name, func(t *testing.T) {
			cfg := &kubeadmapi.InitConfiguration{}
			err := getAPIEndpoint(rt.configMap.data, nodeName, &cfg.LocalAPIEndpoint)
			if rt.expectedError != (err != nil) {
				t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
				return
			}
			if rt.expectedError {
				return
			}

			if cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234 {
				t.Errorf("invalid cfg.APIEndpoint")
			}
		})
	}
}

func TestGetComponentConfigs(t *testing.T) {
	var tests = []struct {
		name          string
		configMaps    []fakeConfigMap
		expectedError bool
	}{
		{
			name: "valid",
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
					},
				},
				{
					name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
					},
				},
			},
		},
		{
			name: "invalid - No kubelet component config ConfigMap",
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeProxyConfigMap,
					data: map[string]string{
						kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
					},
				},
			},
			expectedError: true,
		},
		{
			name: "invalid - No kube-proxy component config ConfigMap",
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion),
					data: map[string]string{
						kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
					},
				},
			},
			expectedError: true,
		},
	}

	for _, rt := range tests {
		t.Run(rt.name, func(t *testing.T) {
			client := clientsetfake.NewSimpleClientset()

			for _, c := range rt.configMaps {
				err := c.create(client)
				if err != nil {
					t.Errorf("couldn't create ConfigMap %s", c.name)
					return
				}
			}

			cfg := &kubeadmapi.InitConfiguration{
				ClusterConfiguration: kubeadmapi.ClusterConfiguration{
					KubernetesVersion: k8sVersionString,
				},
			}
			err := getComponentConfigs(client, &cfg.ClusterConfiguration)
			if rt.expectedError != (err != nil) {
				t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
				return
			}
			if rt.expectedError {
				return
			}

			// Test expected values in InitConfiguration
			if cfg.ComponentConfigs.Kubelet == nil {
				t.Errorf("invalid cfg.ComponentConfigs.Kubelet")
			}
			if cfg.ComponentConfigs.KubeProxy == nil {
				t.Errorf("invalid cfg.ComponentConfigs.KubeProxy")
			}
		})
	}
}

func TestGetInitConfigurationFromCluster(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatalf("Couldn't create tmpdir")
	}
	defer os.RemoveAll(tmpdir)

	var tests = []struct {
		name            string
		fileContents    []byte
		node            *v1.Node
		configMaps      []fakeConfigMap
		newControlPlane bool
		expectedError   bool
	}{
		{
			name:          "invalid - No kubeadm-config ConfigMap",
			expectedError: true,
		},
		{
			name: "invalid - No ClusterConfiguration in kubeadm-config ConfigMap",
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
					data: map[string]string{},
				},
			},
			expectedError: true,
		},
		{
			name: "valid v1beta1 - new control plane == false", // InitConfiguration composed with data from different places, with also node specific information from ClusterStatus and node
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
					data: map[string]string{
						kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta1"]),
						kubeadmconstants.ClusterStatusConfigMapKey:        string(cfgFiles["ClusterStatus_v1beta1"]),
					},
				},
				{
					name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
					},
				},
				{
					name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
					},
				},
			},
			fileContents: kubeletConfFiles["configWithEmbeddedCert"],
			node: &v1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: nodeName,
					Annotations: map[string]string{
						kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
					},
				},
				Spec: v1.NodeSpec{
					Taints: []v1.Taint{kubeadmconstants.ControlPlaneTaint},
				},
			},
		},
		{
			name: "valid v1beta1 - new control plane == true", // InitConfiguration composed with data from different places, without node specific information
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
					data: map[string]string{
						kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta1"]),
					},
				},
				{
					name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
					},
				},
				{
					name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
					},
				},
			},
			newControlPlane: true,
		},
		{
			name: "valid v1beta2 - new control plane == false", // InitConfiguration composed with data from different places, with also node specific information from ClusterStatus and node
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
					data: map[string]string{
						kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta2"]),
						kubeadmconstants.ClusterStatusConfigMapKey:        string(cfgFiles["ClusterStatus_v1beta2"]),
					},
				},
				{
					name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
					},
				},
				{
					name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
					},
				},
			},
			fileContents: kubeletConfFiles["configWithEmbeddedCert"],
			node: &v1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: nodeName,
					Annotations: map[string]string{
						kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
					},
				},
				Spec: v1.NodeSpec{
					Taints: []v1.Taint{kubeadmconstants.ControlPlaneTaint},
				},
			},
		},
		{
			name: "valid v1beta2 - new control plane == true", // InitConfiguration composed with data from different places, without node specific information
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
					data: map[string]string{
						kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta2"]),
					},
				},
				{
					name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
					},
				},
				{
					name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
					data: map[string]string{
						kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
					},
				},
			},
			newControlPlane: true,
		},
	}

	for _, rt := range tests {
		t.Run(rt.name, func(t *testing.T) {
			cfgPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
			if len(rt.fileContents) > 0 {
				err := ioutil.WriteFile(cfgPath, rt.fileContents, 0644)
				if err != nil {
					t.Errorf("Couldn't create file")
					return
				}
			}

			client := clientsetfake.NewSimpleClientset()

			if rt.node != nil {
				_, err := client.CoreV1().Nodes().Create(rt.node)
				if err != nil {
					t.Errorf("couldn't create Node")
					return
				}
			}

			for _, c := range rt.configMaps {
				err := c.create(client)
				if err != nil {
					t.Errorf("couldn't create ConfigMap %s", c.name)
					return
				}
			}

			cfg, err := getInitConfigurationFromCluster(tmpdir, client, rt.newControlPlane)
			if rt.expectedError != (err != nil) {
				t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
				return
			}
			if rt.expectedError {
				return
			}

			// Test expected values in InitConfiguration
			if cfg == nil {
				t.Errorf("unexpected nil return value")
			}
			if cfg.ClusterConfiguration.KubernetesVersion != k8sVersionString {
				t.Errorf("invalid ClusterConfiguration.KubernetesVersion")
			}
			if !rt.newControlPlane && (cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234) {
				t.Errorf("invalid cfg.LocalAPIEndpoint")
			}
			if cfg.ComponentConfigs.Kubelet == nil {
				t.Errorf("invalid cfg.ComponentConfigs.Kubelet")
			}
			if cfg.ComponentConfigs.KubeProxy == nil {
				t.Errorf("invalid cfg.ComponentConfigs.KubeProxy")
			}
		})
	}
}

func TestGetGetClusterStatus(t *testing.T) {
	var tests = []struct {
		name              string
		configMaps        []fakeConfigMap
		expectedEndpoints int
		expectedError     bool
	}{
		{
			name:              "invalid missing config map",
			expectedEndpoints: 0,
		},
		{
			name: "valid v1beta1",
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap,
					data: map[string]string{
						kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta1"]),
					},
				},
			},
			expectedEndpoints: 1,
		},
		{
			name: "valid v1beta2",
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap,
					data: map[string]string{
						kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta2"]),
					},
				},
			},
			expectedEndpoints: 1,
		},
		{
			name: "invalid missing ClusterStatusConfigMapKey in the config map",
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap,
					data: map[string]string{},
				},
			},
			expectedError: true,
		},
		{
			name: "invalid wrong value in the config map",
			configMaps: []fakeConfigMap{
				{
					name: kubeadmconstants.KubeadmConfigConfigMap,
					data: map[string]string{
						kubeadmconstants.ClusterStatusConfigMapKey: "not a kubeadm type",
					},
				},
			},
			expectedError: true,
		},
	}

	for _, rt := range tests {
		t.Run(rt.name, func(t *testing.T) {
			client := clientsetfake.NewSimpleClientset()

			for _, c := range rt.configMaps {
				err := c.create(client)
				if err != nil {
					t.Errorf("couldn't create ConfigMap %s", c.name)
					return
				}
			}

			clusterStatus, err := GetClusterStatus(client)
			if rt.expectedError != (err != nil) {
				t.Errorf("unexpected return err from GetClusterStatus: %v", err)
				return
			}
			if rt.expectedError {
				return
			}

			// Test expected values in clusterStatus
			if len(clusterStatus.APIEndpoints) != rt.expectedEndpoints {
				t.Errorf("unexpected ClusterStatus return value")
			}
		})
	}
}

type fakeConfigMap struct {
	name string
	data map[string]string
}

func (c *fakeConfigMap) create(client clientset.Interface) error {
	return apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      c.name,
			Namespace: metav1.NamespaceSystem,
		},
		Data: c.data,
	})
}
