package validation

import (
	"testing"

	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/util/diff"
	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/kubernetes/pkg/util/validation/field"

	"github.com/openshift/origin/pkg/cmd/server/api"
	configapi "github.com/openshift/origin/pkg/cmd/server/api"
)

func TestFailingAPIServerArgs(t *testing.T) {
	args := configapi.ExtendedArguments{}
	args["port"] = []string{"invalid-value"}
	args["missing-key"] = []string{"value"}

	// [port: invalid value '[invalid-value]': could not be set: strconv.ParseUint: parsing "invalid-value": invalid syntax flag: invalid value 'missing-key': is not a valid flag]

	validationResults := ValidateAPIServerExtendedArguments(args, nil)
	errs := validationResults.Errors

	if len(errs) != 2 {
		t.Fatalf("expected 2 errors, not %v", errs)
	}

	var (
		portErr    *field.Error
		missingErr *field.Error
	)
	for _, err := range errs {
		switch err.Field {
		case "port":
			portErr = err
		case "flag":
			missingErr = err
		}
	}

	if portErr == nil {
		t.Fatalf("missing port")
	}
	if missingErr == nil {
		t.Fatalf("missing missing-key")
	}

	if e, a := "port", portErr.Field; e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
	if e, a := "invalid-value", portErr.BadValue.(string); e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
	if e, a := `could not be set: strconv.ParseInt: parsing "invalid-value": invalid syntax`, portErr.Detail; e != a {
		t.Errorf("expected %v, got %v", e, a)
	}

	if e, a := "flag", missingErr.Field; e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
	if e, a := "missing-key", missingErr.BadValue.(string); e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
	if e, a := `is not a valid flag`, missingErr.Detail; e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
}

func TestFailingControllerArgs(t *testing.T) {
	args := configapi.ExtendedArguments{}
	args["port"] = []string{"invalid-value"}
	args["missing-key"] = []string{"value"}

	// [port: invalid value '[invalid-value]': could not be set: strconv.ParseUint: parsing "invalid-value": invalid syntax flag: invalid value 'missing-key': is not a valid flag]

	errs := ValidateControllerExtendedArguments(args, nil)

	if len(errs) != 2 {
		t.Fatalf("expected 2 errors, not %v", errs)
	}

	var (
		portErr    *field.Error
		missingErr *field.Error
	)
	for _, err := range errs {
		switch err.Field {
		case "port":
			portErr = err
		case "flag":
			missingErr = err
		}
	}

	if portErr == nil {
		t.Fatalf("missing port")
	}
	if missingErr == nil {
		t.Fatalf("missing missing-key")
	}

	if e, a := "port", portErr.Field; e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
	if e, a := "invalid-value", portErr.BadValue.(string); e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
	if e, a := `could not be set: strconv.ParseInt: parsing "invalid-value": invalid syntax`, portErr.Detail; e != a {
		t.Errorf("expected %v, got %v", e, a)
	}

	if e, a := "flag", missingErr.Field; e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
	if e, a := "missing-key", missingErr.BadValue.(string); e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
	if e, a := `is not a valid flag`, missingErr.Detail; e != a {
		t.Errorf("expected %v, got %v", e, a)
	}
}

func TestValidate_ValidateEtcdStorageConfig(t *testing.T) {
	osField := "openShiftStorageVersion"
	kubeField := "kubernetesStorageVersion"
	tests := []struct {
		label                   string
		kubeStorageVersion      string
		openshiftStorageVersion string
		name                    string
		expected                field.ErrorList
	}{
		{
			label:                   "valid levels",
			kubeStorageVersion:      "v1",
			openshiftStorageVersion: "v1",
			expected:                field.ErrorList{},
		},
		{
			label:                   "unknown openshift level",
			kubeStorageVersion:      "v1",
			openshiftStorageVersion: "bogus",
			expected: field.ErrorList{
				field.NotSupported(field.NewPath(osField), "bogus", []string{"v1"}),
			},
		},
		{
			label:                   "unsupported openshift level",
			kubeStorageVersion:      "v1",
			openshiftStorageVersion: "v1beta3",
			expected: field.ErrorList{
				field.NotSupported(field.NewPath(osField), "v1beta3", []string{"v1"}),
			},
		},
		{
			label:                   "missing openshift level",
			kubeStorageVersion:      "v1",
			openshiftStorageVersion: "",
			expected: field.ErrorList{
				field.Required(field.NewPath(osField), ""),
			},
		},
		{
			label:                   "unknown kube level",
			kubeStorageVersion:      "bogus",
			openshiftStorageVersion: "v1",
			expected: field.ErrorList{
				field.NotSupported(field.NewPath(kubeField), "bogus", []string{"v1"}),
			},
		},
		{
			label:                   "unsupported kube level",
			kubeStorageVersion:      "v1beta3",
			openshiftStorageVersion: "v1",
			expected: field.ErrorList{
				field.NotSupported(field.NewPath(kubeField), "v1beta3", []string{"v1"}),
			},
		},
		{
			label:                   "missing kube level",
			kubeStorageVersion:      "",
			openshiftStorageVersion: "v1",
			expected: field.ErrorList{
				field.Required(field.NewPath(kubeField), ""),
			},
		},
	}

	for _, test := range tests {
		t.Logf("evaluating test: %s", test.label)
		config := api.EtcdStorageConfig{
			OpenShiftStorageVersion:  test.openshiftStorageVersion,
			KubernetesStorageVersion: test.kubeStorageVersion,
		}
		results := ValidateEtcdStorageConfig(config, nil)
		if !kapi.Semantic.DeepEqual(test.expected, results) {
			t.Errorf("unexpected validation results; diff:\n%v", diff.ObjectDiff(test.expected, results))
			return
		}
	}
}

func TestValidateAdmissionPluginConfig(t *testing.T) {
	locationOnly := configapi.AdmissionPluginConfig{
		Location: "/some/location",
	}
	configOnly := configapi.AdmissionPluginConfig{
		Configuration: &configapi.NodeConfig{},
	}
	locationAndConfig := configapi.AdmissionPluginConfig{
		Location:      "/some/location",
		Configuration: &configapi.NodeConfig{},
	}
	bothEmpty := configapi.AdmissionPluginConfig{}

	tests := []struct {
		config      map[string]configapi.AdmissionPluginConfig
		expectError bool
	}{
		{
			config: map[string]configapi.AdmissionPluginConfig{
				"one": locationOnly,
				"two": configOnly,
			},
		},
		{
			config: map[string]configapi.AdmissionPluginConfig{
				"one": locationOnly,
				"two": locationAndConfig,
			},
			expectError: true,
		},
		{
			config: map[string]configapi.AdmissionPluginConfig{
				"one": configOnly,
				"two": bothEmpty,
			},
			expectError: true,
		},
	}

	for _, tc := range tests {
		errs := ValidateAdmissionPluginConfig(tc.config, nil)
		if len(errs) > 0 && !tc.expectError {
			t.Errorf("Unexpected error for %#v: %v", tc.config, errs)
		}
		if len(errs) == 0 && tc.expectError {
			t.Errorf("Did not get expected error for: %#v", tc.config)
		}
	}
}

func TestValidateAdmissionPluginConfigConflicts(t *testing.T) {
	testCases := []struct {
		name    string
		options configapi.MasterConfig

		warningFields []string
	}{
		{
			name: "stock everything",
		},
		{
			name: "specified kube admission order 01",
			options: configapi.MasterConfig{
				KubernetesMasterConfig: &configapi.KubernetesMasterConfig{
					AdmissionConfig: configapi.AdmissionConfig{
						PluginOrderOverride: []string{"foo"},
					},
				},
			},
			warningFields: []string{"kubernetesMasterConfig.admissionConfig.pluginOrderOverride"},
		},
		{
			name: "specified kube admission order 02",
			options: configapi.MasterConfig{
				KubernetesMasterConfig: &configapi.KubernetesMasterConfig{
					APIServerArguments: configapi.ExtendedArguments{
						"admission-control": []string{"foo"},
					},
				},
			},
			warningFields: []string{"kubernetesMasterConfig.apiServerArguments[admission-control]"},
		},
		{
			name: "specified origin admission order",
			options: configapi.MasterConfig{
				AdmissionConfig: configapi.AdmissionConfig{
					PluginOrderOverride: []string{"foo"},
				},
			},
			warningFields: []string{"admissionConfig.pluginOrderOverride"},
		},
		{
			name: "specified kube admission config file",
			options: configapi.MasterConfig{
				KubernetesMasterConfig: &configapi.KubernetesMasterConfig{
					APIServerArguments: configapi.ExtendedArguments{
						"admission-control-config-file": []string{"foo"},
					},
				},
			},
			warningFields: []string{"kubernetesMasterConfig.apiServerArguments[admission-control-config-file]"},
		},
		{
			name: "specified, non-conflicting plugin configs 01",
			options: configapi.MasterConfig{
				AdmissionConfig: configapi.AdmissionConfig{
					PluginConfig: map[string]configapi.AdmissionPluginConfig{
						"foo": {
							Location: "bar",
						},
					},
				},
			},
		},
		{
			name: "specified, non-conflicting plugin configs 02",
			options: configapi.MasterConfig{
				KubernetesMasterConfig: &configapi.KubernetesMasterConfig{
					AdmissionConfig: configapi.AdmissionConfig{
						PluginConfig: map[string]configapi.AdmissionPluginConfig{
							"foo": {
								Location: "bar",
							},
							"third": {
								Location: "bar",
							},
						},
					},
				},
				AdmissionConfig: configapi.AdmissionConfig{
					PluginConfig: map[string]configapi.AdmissionPluginConfig{
						"foo": {
							Location: "bar",
						},
					},
				},
			},
		},
		{
			name: "specified, non-conflicting plugin configs 03",
			options: configapi.MasterConfig{
				KubernetesMasterConfig: &configapi.KubernetesMasterConfig{
					AdmissionConfig: configapi.AdmissionConfig{
						PluginConfig: map[string]configapi.AdmissionPluginConfig{
							"foo": {
								Location: "bar",
							},
							"third": {
								Location: "bar",
							},
						},
					},
				},
			},
		},
		{
			name: "specified conflicting plugin configs 01",
			options: configapi.MasterConfig{
				KubernetesMasterConfig: &configapi.KubernetesMasterConfig{
					AdmissionConfig: configapi.AdmissionConfig{
						PluginConfig: map[string]configapi.AdmissionPluginConfig{
							"foo": {
								Location: "different",
							},
						},
					},
				},
				AdmissionConfig: configapi.AdmissionConfig{
					PluginConfig: map[string]configapi.AdmissionPluginConfig{
						"foo": {
							Location: "bar",
						},
					},
				},
			},
			warningFields: []string{"kubernetesMasterConfig.admissionConfig.pluginConfig[foo]"},
		},
	}

	// these fields have warnings in the empty case
	defaultWarningFields := sets.NewString(
		"serviceAccountConfig.managedNames", "serviceAccountConfig.publicKeyFiles", "serviceAccountConfig.privateKeyFile", "serviceAccountConfig.masterCA",
		"projectConfig.securityAllocator", "kubernetesMasterConfig.proxyClientInfo", "auditConfig.auditFilePath")

	for _, tc := range testCases {
		results := ValidateMasterConfig(&tc.options, nil)

		for _, result := range results.Warnings {
			if defaultWarningFields.Has(result.Field) {
				continue
			}

			found := false
			for _, expectedField := range tc.warningFields {
				if result.Field == expectedField {
					found = true
					break
				}
			}

			if !found {
				t.Errorf("%s: didn't expect %q", tc.name, result.Field)
			}
		}

		for _, expectedField := range tc.warningFields {
			found := false
			for _, result := range results.Warnings {
				if result.Field == expectedField {
					found = true
					break
				}
			}

			if !found {
				t.Errorf("%s: didn't find %q", tc.name, expectedField)
			}
		}
	}
}

func TestValidateIngressIPNetworkCIDR(t *testing.T) {
	testCases := []struct {
		testName      string
		cidr          string
		serviceCIDR   string
		clusterCIDR   string
		cloudProvider string
		errorCount    int
	}{
		{
			testName: "No CIDR",
		},
		{
			testName:   "Invalid CIDR",
			cidr:       "foo",
			errorCount: 1,
		},
		{
			testName:    "No cloud provider and conflicting CIDRs",
			cidr:        "172.16.0.0/16",
			serviceCIDR: "172.16.0.0/16",
			clusterCIDR: "172.16.0.0/16",
			errorCount:  2,
		},
		{
			testName: "No cloud provider and unspecified CIDR",
			cidr:     "0.0.0.0/32",
		},
		{
			testName:    "No cloud provider and non-conflicting CIDR",
			cidr:        "172.16.0.0/16",
			serviceCIDR: "172.17.0.0/16",
			clusterCIDR: "172.18.0.0/16",
		},
		{
			testName:      "Cloud provider and unspecified CIDR",
			cidr:          "0.0.0.0/32",
			cloudProvider: "foo",
		},
		{
			testName:      "Cloud provider and CIDR",
			cidr:          "172.16.0.0/16",
			cloudProvider: "foo",
			errorCount:    1,
		},
	}
	for _, test := range testCases {
		config := &configapi.MasterConfig{
			KubernetesMasterConfig: &configapi.KubernetesMasterConfig{
				ControllerArguments: configapi.ExtendedArguments{
					"cloud-provider": []string{test.cloudProvider},
				},
			},
			NetworkConfig: configapi.MasterNetworkConfig{
				IngressIPNetworkCIDR: test.cidr,
				ServiceNetworkCIDR:   test.serviceCIDR,
				ClusterNetworkCIDR:   test.clusterCIDR,
			},
		}
		errors := ValidateIngressIPNetworkCIDR(config, nil)
		errorCount := len(errors)
		if test.errorCount != errorCount {
			t.Errorf("%s: expected %d errors, got %d", test.testName, test.errorCount, errorCount)
		}
	}
}