package admission

import (
	"net"
	"strings"
	"testing"

	"k8s.io/kubernetes/pkg/admission"
	kapi "k8s.io/kubernetes/pkg/api"
)

// TestAdmission verifies various scenarios involving pod/project/global node label selectors
func TestAdmission(t *testing.T) {
	svc := &kapi.Service{
		ObjectMeta: kapi.ObjectMeta{Name: "test"},
	}

	_, ipv4, err := net.ParseCIDR("172.0.0.0/16")
	if err != nil {
		t.Fatal(err)
	}
	_, ipv4subset, err := net.ParseCIDR("172.0.1.0/24")
	if err != nil {
		t.Fatal(err)
	}
	_, ipv4offset, err := net.ParseCIDR("172.200.0.0/24")
	if err != nil {
		t.Fatal(err)
	}
	_, none, err := net.ParseCIDR("0.0.0.0/32")
	if err != nil {
		t.Fatal(err)
	}
	_, all, err := net.ParseCIDR("0.0.0.0/0")
	if err != nil {
		t.Fatal(err)
	}

	tests := []struct {
		testName        string
		rejects, admits []*net.IPNet
		op              admission.Operation
		externalIPs     []string
		admit           bool
		errFn           func(err error) bool
	}{
		{
			admit:    true,
			op:       admission.Create,
			testName: "No external IPs on create",
		},
		{
			admit:    true,
			op:       admission.Update,
			testName: "No external IPs on update",
		},
		{
			admit:       false,
			externalIPs: []string{"1.2.3.4"},
			op:          admission.Create,
			testName:    "No external IPs allowed on create",
			errFn:       func(err error) bool { return strings.Contains(err.Error(), "externalIPs have been disabled") },
		},
		{
			admit:       false,
			externalIPs: []string{"1.2.3.4"},
			op:          admission.Update,
			testName:    "No external IPs allowed on update",
			errFn:       func(err error) bool { return strings.Contains(err.Error(), "externalIPs have been disabled") },
		},
		{
			admit:       false,
			admits:      []*net.IPNet{ipv4},
			externalIPs: []string{"1.2.3.4"},
			op:          admission.Create,
			testName:    "IP out of range on create",
			errFn: func(err error) bool {
				return strings.Contains(err.Error(), "externalIP is not allowed") &&
					strings.Contains(err.Error(), "spec.externalIPs[0]")
			},
		},
		{
			admit:       false,
			admits:      []*net.IPNet{ipv4},
			externalIPs: []string{"1.2.3.4"},
			op:          admission.Update,
			testName:    "IP out of range on update",
			errFn: func(err error) bool {
				return strings.Contains(err.Error(), "externalIP is not allowed") &&
					strings.Contains(err.Error(), "spec.externalIPs[0]")
			},
		},
		{
			admit:       false,
			admits:      []*net.IPNet{ipv4},
			rejects:     []*net.IPNet{ipv4subset},
			externalIPs: []string{"172.0.1.1"},
			op:          admission.Update,
			testName:    "IP out of range due to blacklist",
			errFn: func(err error) bool {
				return strings.Contains(err.Error(), "externalIP is not allowed") &&
					strings.Contains(err.Error(), "spec.externalIPs[0]")
			},
		},
		{
			admit:       false,
			admits:      []*net.IPNet{ipv4},
			rejects:     []*net.IPNet{ipv4offset},
			externalIPs: []string{"172.199.1.1"},
			op:          admission.Update,
			testName:    "IP not in reject or admit",
			errFn: func(err error) bool {
				return strings.Contains(err.Error(), "externalIP is not allowed") &&
					strings.Contains(err.Error(), "spec.externalIPs[0]")
			},
		},
		{
			admit:       true,
			admits:      []*net.IPNet{ipv4},
			externalIPs: []string{"172.0.0.1"},
			op:          admission.Create,
			testName:    "IP in range on create",
		},
		{
			admit:       true,
			admits:      []*net.IPNet{ipv4},
			externalIPs: []string{"172.0.0.1"},
			op:          admission.Update,
			testName:    "IP in range on update",
		},

		// other checks
		{
			admit:       false,
			admits:      []*net.IPNet{ipv4},
			externalIPs: []string{"abcd"},
			op:          admission.Create,
			testName:    "IP unparseable on create",
			errFn: func(err error) bool {
				return strings.Contains(err.Error(), "externalIPs must be a valid address") &&
					strings.Contains(err.Error(), "spec.externalIPs[0]")
			},
		},
		{
			admit:       false,
			admits:      []*net.IPNet{none},
			externalIPs: []string{"1.2.3.4"},
			op:          admission.Create,
			testName:    "IP range is empty",
		},
		{
			admit:       false,
			rejects:     []*net.IPNet{all},
			admits:      []*net.IPNet{all},
			externalIPs: []string{"1.2.3.4"},
			op:          admission.Create,
			testName:    "rejections can cover the entire range",
		},
	}
	for _, test := range tests {
		svc.Spec.ExternalIPs = test.externalIPs
		handler := NewExternalIPRanger(test.rejects, test.admits)

		err := handler.Admit(admission.NewAttributesRecord(svc, nil, kapi.Kind("Service").WithVersion("version"), "namespace", svc.ObjectMeta.Name, kapi.Resource("services").WithVersion("version"), "", test.op, nil))
		if test.admit && err != nil {
			t.Errorf("%s: expected no error but got: %s", test.testName, err)
		} else if !test.admit && err == nil {
			t.Errorf("%s: expected an error", test.testName)
		}
		if test.errFn != nil && !test.errFn(err) {
			t.Errorf("%s: unexpected error: %v", test.testName, err)
		}
	}
}

func TestHandles(t *testing.T) {
	for op, shouldHandle := range map[admission.Operation]bool{
		admission.Create:  true,
		admission.Update:  true,
		admission.Connect: false,
		admission.Delete:  false,
	} {
		ranger := NewExternalIPRanger(nil, nil)
		if e, a := shouldHandle, ranger.Handles(op); e != a {
			t.Errorf("%v: shouldHandle=%t, handles=%t", op, e, a)
		}
	}
}