// Copyright 2016 The Prometheus 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 kubernetes

import (
	"context"
	"testing"

	"github.com/prometheus/common/model"
	v1 "k8s.io/api/core/v1"
	disv1beta1 "k8s.io/api/discovery/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"

	"github.com/prometheus/prometheus/discovery/targetgroup"
)

func strptr(str string) *string {
	return &str
}

func boolptr(b bool) *bool {
	return &b
}

func int32ptr(i int32) *int32 {
	return &i
}

func protocolptr(p v1.Protocol) *v1.Protocol {
	return &p
}

func makeEndpointSlice() *disv1beta1.EndpointSlice {
	return &disv1beta1.EndpointSlice{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "testendpoints",
			Namespace: "default",
			Labels: map[string]string{
				disv1beta1.LabelServiceName: "testendpoints",
			},
		},
		AddressType: disv1beta1.AddressTypeIPv4,
		Ports: []disv1beta1.EndpointPort{
			{
				Name:     strptr("testport"),
				Port:     int32ptr(9000),
				Protocol: protocolptr(v1.ProtocolTCP),
			},
		},
		Endpoints: []disv1beta1.Endpoint{
			{
				Addresses: []string{"1.2.3.4"},
				Hostname:  strptr("testendpoint1"),
			}, {
				Addresses: []string{"2.3.4.5"},
				Conditions: disv1beta1.EndpointConditions{
					Ready: boolptr(true),
				},
			}, {
				Addresses: []string{"3.4.5.6"},
				Conditions: disv1beta1.EndpointConditions{
					Ready: boolptr(false),
				},
			},
		},
	}
}

func TestEndpointSliceDiscoveryBeforeRun(t *testing.T) {
	n, c := makeDiscovery(RoleEndpointSlice, NamespaceDiscovery{})

	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			obj := makeEndpointSlice()
			c.DiscoveryV1beta1().EndpointSlices(obj.Namespace).Create(context.Background(), obj, metav1.CreateOptions{})
		},
		expectedMaxItems: 1,
		expectedRes: map[string]*targetgroup.Group{
			"endpointslice/default/testendpoints": {
				Targets: []model.LabelSet{
					{
						"__address__": "1.2.3.4:9000",
						"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
						"__meta_kubernetes_endpointslice_port":              "9000",
						"__meta_kubernetes_endpointslice_port_name":         "testport",
						"__meta_kubernetes_endpointslice_port_protocol":     "TCP",
					},
					{
						"__address__": "2.3.4.5:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
					{
						"__address__": "3.4.5.6:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_endpointslice_address_type": "IPv4",
					"__meta_kubernetes_namespace":                  "default",
					"__meta_kubernetes_endpointslice_name":         "testendpoints",
				},
				Source: "endpointslice/default/testendpoints",
			},
		},
	}.Run(t)
}

func TestEndpointSliceDiscoveryAdd(t *testing.T) {
	obj := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "testpod",
			Namespace: "default",
			UID:       types.UID("deadbeef"),
		},
		Spec: v1.PodSpec{
			NodeName: "testnode",
			Containers: []v1.Container{
				{
					Name: "c1",
					Ports: []v1.ContainerPort{
						{
							Name:          "mainport",
							ContainerPort: 9000,
							Protocol:      v1.ProtocolTCP,
						},
					},
				},
				{
					Name: "c2",
					Ports: []v1.ContainerPort{
						{
							Name:          "sideport",
							ContainerPort: 9001,
							Protocol:      v1.ProtocolTCP,
						},
					},
				},
			},
		},
		Status: v1.PodStatus{
			HostIP: "2.3.4.5",
			PodIP:  "1.2.3.4",
		},
	}
	n, c := makeDiscovery(RoleEndpointSlice, NamespaceDiscovery{}, obj)

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			obj := &disv1beta1.EndpointSlice{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testendpoints",
					Namespace: "default",
				},
				AddressType: disv1beta1.AddressTypeIPv4,
				Ports: []disv1beta1.EndpointPort{
					{
						Name:     strptr("testport"),
						Port:     int32ptr(9000),
						Protocol: protocolptr(v1.ProtocolTCP),
					},
				},
				Endpoints: []disv1beta1.Endpoint{
					{
						Addresses: []string{"4.3.2.1"},
						TargetRef: &v1.ObjectReference{
							Kind:      "Pod",
							Name:      "testpod",
							Namespace: "default",
						},
						Conditions: disv1beta1.EndpointConditions{
							Ready: boolptr(false),
						},
					},
				},
			}
			c.DiscoveryV1beta1().EndpointSlices(obj.Namespace).Create(context.Background(), obj, metav1.CreateOptions{})
		},
		expectedMaxItems: 1,
		expectedRes: map[string]*targetgroup.Group{
			"endpointslice/default/testendpoints": {
				Targets: []model.LabelSet{
					{
						"__address__": "4.3.2.1:9000",
						"__meta_kubernetes_endpointslice_address_target_kind":       "Pod",
						"__meta_kubernetes_endpointslice_address_target_name":       "testpod",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
						"__meta_kubernetes_pod_container_name":                      "c1",
						"__meta_kubernetes_pod_container_port_name":                 "mainport",
						"__meta_kubernetes_pod_container_port_number":               "9000",
						"__meta_kubernetes_pod_container_port_protocol":             "TCP",
						"__meta_kubernetes_pod_host_ip":                             "2.3.4.5",
						"__meta_kubernetes_pod_ip":                                  "1.2.3.4",
						"__meta_kubernetes_pod_name":                                "testpod",
						"__meta_kubernetes_pod_node_name":                           "testnode",
						"__meta_kubernetes_pod_phase":                               "",
						"__meta_kubernetes_pod_ready":                               "unknown",
						"__meta_kubernetes_pod_uid":                                 "deadbeef",
					},
					{
						"__address__":                                   "1.2.3.4:9001",
						"__meta_kubernetes_pod_container_name":          "c2",
						"__meta_kubernetes_pod_container_port_name":     "sideport",
						"__meta_kubernetes_pod_container_port_number":   "9001",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_host_ip":                 "2.3.4.5",
						"__meta_kubernetes_pod_ip":                      "1.2.3.4",
						"__meta_kubernetes_pod_name":                    "testpod",
						"__meta_kubernetes_pod_node_name":               "testnode",
						"__meta_kubernetes_pod_phase":                   "",
						"__meta_kubernetes_pod_ready":                   "unknown",
						"__meta_kubernetes_pod_uid":                     "deadbeef",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_endpointslice_address_type": "IPv4",
					"__meta_kubernetes_endpointslice_name":         "testendpoints",
					"__meta_kubernetes_namespace":                  "default",
				},
				Source: "endpointslice/default/testendpoints",
			},
		},
	}.Run(t)
}

func TestEndpointSliceDiscoveryDelete(t *testing.T) {
	n, c := makeDiscovery(RoleEndpointSlice, NamespaceDiscovery{}, makeEndpointSlice())

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			obj := makeEndpointSlice()
			c.DiscoveryV1beta1().EndpointSlices(obj.Namespace).Delete(context.Background(), obj.Name, metav1.DeleteOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"endpointslice/default/testendpoints": {
				Source: "endpointslice/default/testendpoints",
			},
		},
	}.Run(t)
}

func TestEndpointSliceDiscoveryUpdate(t *testing.T) {
	n, c := makeDiscovery(RoleEndpointSlice, NamespaceDiscovery{}, makeEndpointSlice())

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			obj := &disv1beta1.EndpointSlice{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testendpoints",
					Namespace: "default",
				},
				AddressType: disv1beta1.AddressTypeIPv4,
				Ports: []disv1beta1.EndpointPort{
					{
						Name:     strptr("testport"),
						Port:     int32ptr(9000),
						Protocol: protocolptr(v1.ProtocolTCP),
					},
				},
				Endpoints: []disv1beta1.Endpoint{
					{
						Addresses: []string{"1.2.3.4"},
						Hostname:  strptr("testendpoint1"),
					}, {
						Addresses: []string{"2.3.4.5"},
						Conditions: disv1beta1.EndpointConditions{
							Ready: boolptr(true),
						},
					},
				},
			}
			c.DiscoveryV1beta1().EndpointSlices(obj.Namespace).Update(context.Background(), obj, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"endpointslice/default/testendpoints": {
				Targets: []model.LabelSet{
					{
						"__address__": "1.2.3.4:9000",
						"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
						"__meta_kubernetes_endpointslice_port":              "9000",
						"__meta_kubernetes_endpointslice_port_name":         "testport",
						"__meta_kubernetes_endpointslice_port_protocol":     "TCP",
					},
					{
						"__address__": "2.3.4.5:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_endpointslice_address_type": "IPv4",
					"__meta_kubernetes_endpointslice_name":         "testendpoints",
					"__meta_kubernetes_namespace":                  "default",
				},
				Source: "endpointslice/default/testendpoints",
			},
		},
	}.Run(t)
}

func TestEndpointSliceDiscoveryEmptyEndpoints(t *testing.T) {
	n, c := makeDiscovery(RoleEndpointSlice, NamespaceDiscovery{}, makeEndpointSlice())

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			obj := &disv1beta1.EndpointSlice{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testendpoints",
					Namespace: "default",
				},
				AddressType: disv1beta1.AddressTypeIPv4,
				Ports: []disv1beta1.EndpointPort{
					{
						Name:     strptr("testport"),
						Port:     int32ptr(9000),
						Protocol: protocolptr(v1.ProtocolTCP),
					},
				},
				Endpoints: []disv1beta1.Endpoint{},
			}
			c.DiscoveryV1beta1().EndpointSlices(obj.Namespace).Update(context.Background(), obj, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"endpointslice/default/testendpoints": {
				Labels: model.LabelSet{
					"__meta_kubernetes_endpointslice_address_type": "IPv4",
					"__meta_kubernetes_endpointslice_name":         "testendpoints",
					"__meta_kubernetes_namespace":                  "default",
				},
				Source: "endpointslice/default/testendpoints",
			},
		},
	}.Run(t)
}

func TestEndpointSliceDiscoveryWithService(t *testing.T) {
	n, c := makeDiscovery(RoleEndpointSlice, NamespaceDiscovery{}, makeEndpointSlice())

	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			obj := &v1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testendpoints",
					Namespace: "default",
					Labels: map[string]string{
						"app/name": "test",
					},
				},
			}
			c.CoreV1().Services(obj.Namespace).Create(context.Background(), obj, metav1.CreateOptions{})
		},
		expectedMaxItems: 1,
		expectedRes: map[string]*targetgroup.Group{
			"endpointslice/default/testendpoints": {
				Targets: []model.LabelSet{
					{
						"__address__": "1.2.3.4:9000",
						"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
						"__meta_kubernetes_endpointslice_port":              "9000",
						"__meta_kubernetes_endpointslice_port_name":         "testport",
						"__meta_kubernetes_endpointslice_port_protocol":     "TCP",
					},
					{
						"__address__": "2.3.4.5:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
					{
						"__address__": "3.4.5.6:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_endpointslice_address_type":    "IPv4",
					"__meta_kubernetes_endpointslice_name":            "testendpoints",
					"__meta_kubernetes_namespace":                     "default",
					"__meta_kubernetes_service_label_app_name":        "test",
					"__meta_kubernetes_service_labelpresent_app_name": "true",
					"__meta_kubernetes_service_name":                  "testendpoints",
				},
				Source: "endpointslice/default/testendpoints",
			},
		},
	}.Run(t)
}

func TestEndpointSliceDiscoveryWithServiceUpdate(t *testing.T) {
	n, c := makeDiscovery(RoleEndpointSlice, NamespaceDiscovery{}, makeEndpointSlice())

	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			obj := &v1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testendpoints",
					Namespace: "default",
					Labels: map[string]string{
						"app/name": "test",
					},
				},
			}
			c.CoreV1().Services(obj.Namespace).Create(context.Background(), obj, metav1.CreateOptions{})
		},
		afterStart: func() {
			obj := &v1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testendpoints",
					Namespace: "default",
					Labels: map[string]string{
						"app/name":  "svc",
						"component": "testing",
					},
				},
			}
			c.CoreV1().Services(obj.Namespace).Update(context.Background(), obj, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"endpointslice/default/testendpoints": {
				Targets: []model.LabelSet{
					{
						"__address__": "1.2.3.4:9000",
						"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
						"__meta_kubernetes_endpointslice_port":              "9000",
						"__meta_kubernetes_endpointslice_port_name":         "testport",
						"__meta_kubernetes_endpointslice_port_protocol":     "TCP",
					},
					{
						"__address__": "2.3.4.5:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
					{
						"__address__": "3.4.5.6:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_endpointslice_address_type":     "IPv4",
					"__meta_kubernetes_endpointslice_name":             "testendpoints",
					"__meta_kubernetes_namespace":                      "default",
					"__meta_kubernetes_service_label_app_name":         "svc",
					"__meta_kubernetes_service_label_component":        "testing",
					"__meta_kubernetes_service_labelpresent_app_name":  "true",
					"__meta_kubernetes_service_labelpresent_component": "true",
					"__meta_kubernetes_service_name":                   "testendpoints",
				},
				Source: "endpointslice/default/testendpoints",
			},
		},
	}.Run(t)
}

func TestEndpointSliceDiscoveryNamespaces(t *testing.T) {
	epOne := makeEndpointSlice()
	epOne.Namespace = "ns1"
	objs := []runtime.Object{
		epOne,
		&disv1beta1.EndpointSlice{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testendpoints",
				Namespace: "ns2",
			},
			AddressType: disv1beta1.AddressTypeIPv4,
			Ports: []disv1beta1.EndpointPort{
				{
					Name:     strptr("testport"),
					Port:     int32ptr(9000),
					Protocol: protocolptr(v1.ProtocolTCP),
				},
			},
			Endpoints: []disv1beta1.Endpoint{
				{
					Addresses: []string{"4.3.2.1"},
					TargetRef: &v1.ObjectReference{
						Kind:      "Pod",
						Name:      "testpod",
						Namespace: "ns2",
					},
				},
			},
		},
		&v1.Service{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testendpoints",
				Namespace: "ns1",
				Labels: map[string]string{
					"app": "app1",
				},
			},
		},
		&v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testpod",
				Namespace: "ns2",
				UID:       types.UID("deadbeef"),
			},
			Spec: v1.PodSpec{
				NodeName: "testnode",
				Containers: []v1.Container{
					{
						Name: "c1",
						Ports: []v1.ContainerPort{
							{
								Name:          "mainport",
								ContainerPort: 9000,
								Protocol:      v1.ProtocolTCP,
							},
						},
					},
				},
			},
			Status: v1.PodStatus{
				HostIP: "2.3.4.5",
				PodIP:  "4.3.2.1",
			},
		},
	}
	n, _ := makeDiscovery(RoleEndpointSlice, NamespaceDiscovery{Names: []string{"ns1", "ns2"}}, objs...)

	k8sDiscoveryTest{
		discovery:        n,
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"endpointslice/ns1/testendpoints": {
				Targets: []model.LabelSet{
					{
						"__address__": "1.2.3.4:9000",
						"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
						"__meta_kubernetes_endpointslice_port":              "9000",
						"__meta_kubernetes_endpointslice_port_name":         "testport",
						"__meta_kubernetes_endpointslice_port_protocol":     "TCP",
					},
					{
						"__address__": "2.3.4.5:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
					{
						"__address__": "3.4.5.6:9000",
						"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
						"__meta_kubernetes_endpointslice_port":                      "9000",
						"__meta_kubernetes_endpointslice_port_name":                 "testport",
						"__meta_kubernetes_endpointslice_port_protocol":             "TCP",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_endpointslice_address_type": "IPv4",
					"__meta_kubernetes_endpointslice_name":         "testendpoints",
					"__meta_kubernetes_namespace":                  "ns1",
					"__meta_kubernetes_service_label_app":          "app1",
					"__meta_kubernetes_service_labelpresent_app":   "true",
					"__meta_kubernetes_service_name":               "testendpoints",
				},
				Source: "endpointslice/ns1/testendpoints",
			},
			"endpointslice/ns2/testendpoints": {
				Targets: []model.LabelSet{
					{
						"__address__": "4.3.2.1:9000",
						"__meta_kubernetes_endpointslice_address_target_kind": "Pod",
						"__meta_kubernetes_endpointslice_address_target_name": "testpod",
						"__meta_kubernetes_endpointslice_port":                "9000",
						"__meta_kubernetes_endpointslice_port_name":           "testport",
						"__meta_kubernetes_endpointslice_port_protocol":       "TCP",
						"__meta_kubernetes_pod_container_name":                "c1",
						"__meta_kubernetes_pod_container_port_name":           "mainport",
						"__meta_kubernetes_pod_container_port_number":         "9000",
						"__meta_kubernetes_pod_container_port_protocol":       "TCP",
						"__meta_kubernetes_pod_host_ip":                       "2.3.4.5",
						"__meta_kubernetes_pod_ip":                            "4.3.2.1",
						"__meta_kubernetes_pod_name":                          "testpod",
						"__meta_kubernetes_pod_node_name":                     "testnode",
						"__meta_kubernetes_pod_phase":                         "",
						"__meta_kubernetes_pod_ready":                         "unknown",
						"__meta_kubernetes_pod_uid":                           "deadbeef",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_endpointslice_address_type": "IPv4",
					"__meta_kubernetes_endpointslice_name":         "testendpoints",
					"__meta_kubernetes_namespace":                  "ns2",
				},
				Source: "endpointslice/ns2/testendpoints",
			},
		},
	}.Run(t)
}