package integration

import (
	"testing"

	kapi "k8s.io/kubernetes/pkg/api"
	kapierrors "k8s.io/kubernetes/pkg/api/errors"
	"k8s.io/kubernetes/pkg/api/unversioned"
	"k8s.io/kubernetes/pkg/apis/batch"
	"k8s.io/kubernetes/pkg/apis/extensions"
	kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"

	"github.com/openshift/origin/pkg/client"
	policy "github.com/openshift/origin/pkg/cmd/admin/policy"
	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
	deployapi "github.com/openshift/origin/pkg/deploy/api"
	pluginapi "github.com/openshift/origin/pkg/scheduler/admission/podnodeconstraints/api"
	testutil "github.com/openshift/origin/test/util"
	testserver "github.com/openshift/origin/test/util/server"
)

func TestPodNodeConstraintsAdmissionPluginSetNodeNameClusterAdmin(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	oclient, kclientset := setupClusterAdminPodNodeConstraintsTest(t, &pluginapi.PodNodeConstraintsConfig{})
	testPodNodeConstraintsObjectCreationWithPodTemplate(t, "set node name, cluster admin", kclientset, oclient, "nodename.example.com", nil, false)
}

func TestPodNodeConstraintsAdmissionPluginSetNodeNameNonAdmin(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	config := &pluginapi.PodNodeConstraintsConfig{}
	oclient, kclientset := setupUserPodNodeConstraintsTest(t, config, "derples")
	testPodNodeConstraintsObjectCreationWithPodTemplate(t, "set node name, regular user", kclientset, oclient, "nodename.example.com", nil, true)
}

func TestPodNodeConstraintsAdmissionPluginSetNodeSelectorClusterAdmin(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	config := &pluginapi.PodNodeConstraintsConfig{
		NodeSelectorLabelBlacklist: []string{"hostname"},
	}
	oclient, kclientset := setupClusterAdminPodNodeConstraintsTest(t, config)
	testPodNodeConstraintsObjectCreationWithPodTemplate(t, "set node selector, cluster admin", kclientset, oclient, "", map[string]string{"hostname": "foo"}, false)
}

func TestPodNodeConstraintsAdmissionPluginSetNodeSelectorNonAdmin(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	config := &pluginapi.PodNodeConstraintsConfig{
		NodeSelectorLabelBlacklist: []string{"hostname"},
	}
	oclient, kclientset := setupUserPodNodeConstraintsTest(t, config, "derples")
	testPodNodeConstraintsObjectCreationWithPodTemplate(t, "set node selector, regular user", kclientset, oclient, "", map[string]string{"hostname": "foo"}, true)
}

func setupClusterAdminPodNodeConstraintsTest(t *testing.T, pluginConfig *pluginapi.PodNodeConstraintsConfig) (*client.Client, *kclientset.Clientset) {
	testutil.RequireEtcd(t)
	masterConfig, err := testserver.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("error creating config: %v", err)
	}
	cfg := map[string]configapi.AdmissionPluginConfig{
		"PodNodeConstraints": {
			Configuration: pluginConfig,
		},
	}
	masterConfig.AdmissionConfig.PluginConfig = cfg
	masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = cfg

	kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig)
	if err != nil {
		t.Fatalf("error starting server: %v", err)
	}
	kubeClientset, err := testutil.GetClusterAdminKubeClient(kubeConfigFile)
	if err != nil {
		t.Fatalf("error getting client: %v", err)
	}
	openShiftClient, err := testutil.GetClusterAdminClient(kubeConfigFile)
	if err != nil {
		t.Fatalf("error getting client: %v", err)
	}
	ns := &kapi.Namespace{}
	ns.Name = testutil.Namespace()
	_, err = kubeClientset.Core().Namespaces().Create(ns)
	if err != nil {
		t.Fatalf("error creating namespace: %v", err)
	}
	if err := testserver.WaitForPodCreationServiceAccounts(kubeClientset, testutil.Namespace()); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	return openShiftClient, kubeClientset
}

func setupUserPodNodeConstraintsTest(t *testing.T, pluginConfig *pluginapi.PodNodeConstraintsConfig, user string) (*client.Client, kclientset.Interface) {
	testutil.RequireEtcd(t)
	masterConfig, err := testserver.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("error creating config: %v", err)
	}
	cfg := map[string]configapi.AdmissionPluginConfig{
		"PodNodeConstraints": {
			Configuration: pluginConfig,
		},
	}
	masterConfig.AdmissionConfig.PluginConfig = cfg
	masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = cfg
	kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig)
	if err != nil {
		t.Fatalf("error starting server: %v", err)
	}
	clusterAdminClient, err := testutil.GetClusterAdminClient(kubeConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(kubeConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	userClient, userkubeClientset, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, user)
	if err != nil {
		t.Fatalf("error getting user/kube client: %v", err)
	}
	kubeClientset, err := testutil.GetClusterAdminKubeClient(kubeConfigFile)
	if err != nil {
		t.Fatalf("error getting kube client: %v", err)
	}
	ns := &kapi.Namespace{}
	ns.Name = testutil.Namespace()
	_, err = kubeClientset.Core().Namespaces().Create(ns)
	if err != nil {
		t.Fatalf("error creating namespace: %v", err)
	}
	if err := testserver.WaitForServiceAccounts(kubeClientset, testutil.Namespace(), []string{bootstrappolicy.DefaultServiceAccountName}); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	addUser := &policy.RoleModificationOptions{
		RoleNamespace:       ns.Name,
		RoleName:            bootstrappolicy.AdminRoleName,
		RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient),
		Users:               []string{user},
	}
	if err := addUser.AddRole(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	return userClient, userkubeClientset
}

func testPodNodeConstraintsPodSpec(nodeName string, nodeSelector map[string]string) kapi.PodSpec {
	spec := kapi.PodSpec{}
	spec.RestartPolicy = kapi.RestartPolicyAlways
	spec.NodeName = nodeName
	spec.NodeSelector = nodeSelector
	spec.Containers = []kapi.Container{
		{
			Name:  "container",
			Image: "test/image",
		},
	}
	return spec
}

func testPodNodeConstraintsPod(nodeName string, nodeSelector map[string]string) *kapi.Pod {
	pod := &kapi.Pod{}
	pod.Name = "testpod"
	pod.Spec = testPodNodeConstraintsPodSpec(nodeName, nodeSelector)
	return pod
}

func testPodNodeConstraintsReplicationController(nodeName string, nodeSelector map[string]string) *kapi.ReplicationController {
	rc := &kapi.ReplicationController{}
	rc.Name = "testrc"
	rc.Spec.Replicas = 1
	rc.Spec.Selector = map[string]string{"foo": "bar"}
	rc.Spec.Template = &kapi.PodTemplateSpec{}
	rc.Spec.Template.Labels = map[string]string{"foo": "bar"}
	rc.Spec.Template.Spec = testPodNodeConstraintsPodSpec(nodeName, nodeSelector)
	return rc
}

func testPodNodeConstraintsDeployment(nodeName string, nodeSelector map[string]string) *extensions.Deployment {
	d := &extensions.Deployment{}
	d.Name = "testdeployment"
	d.Spec.Replicas = 1
	d.Spec.Template.Labels = map[string]string{"foo": "bar"}
	d.Spec.Template.Spec = testPodNodeConstraintsPodSpec(nodeName, nodeSelector)
	d.Spec.Selector = &unversioned.LabelSelector{
		MatchLabels: map[string]string{"foo": "bar"},
	}
	return d
}

func testPodNodeConstraintsReplicaSet(nodeName string, nodeSelector map[string]string) *extensions.ReplicaSet {
	rs := &extensions.ReplicaSet{}
	rs.Name = "testrs"
	rs.Spec.Replicas = 1
	rs.Spec.Template = kapi.PodTemplateSpec{}
	rs.Spec.Template.Labels = map[string]string{"foo": "bar"}
	rs.Spec.Template.Spec = testPodNodeConstraintsPodSpec(nodeName, nodeSelector)
	rs.Spec.Selector = &unversioned.LabelSelector{
		MatchLabels: map[string]string{"foo": "bar"},
	}
	return rs
}

func testPodNodeConstraintsJob(nodeName string, nodeSelector map[string]string) *batch.Job {
	job := &batch.Job{}
	job.Name = "testjob"
	job.Spec.Template.Labels = map[string]string{"foo": "bar"}
	job.Spec.Template.Spec = testPodNodeConstraintsPodSpec(nodeName, nodeSelector)
	job.Spec.Template.Spec.RestartPolicy = kapi.RestartPolicyNever
	// Matching selector is now generated automatically
	// job.Spec.Selector = ...
	return job
}

func testPodNodeConstraintsDeploymentConfig(nodeName string, nodeSelector map[string]string) *deployapi.DeploymentConfig {
	dc := &deployapi.DeploymentConfig{}
	dc.Name = "testdc"
	dc.Spec.Replicas = 1
	dc.Spec.Template = &kapi.PodTemplateSpec{}
	dc.Spec.Template.Labels = map[string]string{"foo": "bar"}
	dc.Spec.Template.Spec = testPodNodeConstraintsPodSpec(nodeName, nodeSelector)
	dc.Spec.Selector = map[string]string{"foo": "bar"}
	return dc
}

// testPodNodeConstraintsObjectCreationWithPodTemplate attempts to create different object types that contain pod templates
// using the passed in nodeName and nodeSelector. It will use the expectError flag to determine if an error should be returned or not
func testPodNodeConstraintsObjectCreationWithPodTemplate(t *testing.T, name string, kclientset kclientset.Interface, client client.Interface, nodeName string, nodeSelector map[string]string, expectError bool) {

	checkForbiddenErr := func(objType string, err error) {
		if err == nil && expectError {
			t.Errorf("%s (%s): expected forbidden error but did not receive one", name, objType)
			return
		}
		if err != nil && !expectError {
			t.Errorf("%s (%s): got error but did not expect one: %v", name, objType, err)
			return
		}
		if err != nil && expectError && !kapierrors.IsForbidden(err) {
			t.Errorf("%s (%s): did not get an expected forbidden error: %v", name, objType, err)
			return
		}
	}

	// Pod
	pod := testPodNodeConstraintsPod(nodeName, nodeSelector)
	_, err := kclientset.Core().Pods(testutil.Namespace()).Create(pod)
	checkForbiddenErr("pod", err)

	// ReplicationController
	rc := testPodNodeConstraintsReplicationController(nodeName, nodeSelector)
	_, err = kclientset.Core().ReplicationControllers(testutil.Namespace()).Create(rc)
	checkForbiddenErr("rc", err)

	// TODO: Enable when the deployments endpoint is supported in Origin
	// Deployment
	// d := testPodNodeConstraintsDeployment(nodeName, nodeSelector)
	// _, err = kclientset.Extensions().Deployments(testutil.Namespace()).Create(d)
	// checkForbiddenErr("deployment", err)

	// ReplicaSet
	rs := testPodNodeConstraintsReplicaSet(nodeName, nodeSelector)
	_, err = kclientset.Extensions().ReplicaSets(testutil.Namespace()).Create(rs)
	checkForbiddenErr("replicaset", err)

	// Job
	job := testPodNodeConstraintsJob(nodeName, nodeSelector)
	_, err = kclientset.Batch().Jobs(testutil.Namespace()).Create(job)
	checkForbiddenErr("job", err)

	// DeploymentConfig
	dc := testPodNodeConstraintsDeploymentConfig(nodeName, nodeSelector)
	_, err = client.DeploymentConfigs(testutil.Namespace()).Create(dc)
	checkForbiddenErr("dc", err)
}
