123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /*
- Copyright 2019 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 endpointslice
- import (
- "fmt"
- "reflect"
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- v1 "k8s.io/api/core/v1"
- discovery "k8s.io/api/discovery/v1beta1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/util/intstr"
- "k8s.io/apimachinery/pkg/util/rand"
- "k8s.io/client-go/kubernetes/fake"
- k8stesting "k8s.io/client-go/testing"
- "k8s.io/client-go/tools/cache"
- endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
- utilpointer "k8s.io/utils/pointer"
- )
- func TestNewEndpointSlice(t *testing.T) {
- ipAddressType := discovery.AddressTypeIPv4
- portName := "foo"
- protocol := v1.ProtocolTCP
- endpointMeta := endpointMeta{
- Ports: []discovery.EndpointPort{{Name: &portName, Protocol: &protocol}},
- AddressType: ipAddressType,
- }
- service := v1.Service{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"},
- Spec: v1.ServiceSpec{
- Ports: []v1.ServicePort{{Port: 80}},
- Selector: map[string]string{"foo": "bar"},
- },
- }
- gvk := schema.GroupVersionKind{Version: "v1", Kind: "Service"}
- ownerRef := metav1.NewControllerRef(&service, gvk)
- expectedSlice := discovery.EndpointSlice{
- ObjectMeta: metav1.ObjectMeta{
- Labels: map[string]string{
- discovery.LabelServiceName: service.Name,
- discovery.LabelManagedBy: controllerName,
- },
- GenerateName: fmt.Sprintf("%s-", service.Name),
- OwnerReferences: []metav1.OwnerReference{*ownerRef},
- Namespace: service.Namespace,
- },
- Ports: endpointMeta.Ports,
- AddressType: endpointMeta.AddressType,
- Endpoints: []discovery.Endpoint{},
- }
- generatedSlice := newEndpointSlice(&service, &endpointMeta)
- assert.EqualValues(t, expectedSlice, *generatedSlice)
- }
- func TestPodToEndpoint(t *testing.T) {
- ns := "test"
- svc, _ := newServiceAndEndpointMeta("foo", ns)
- svcPublishNotReady, _ := newServiceAndEndpointMeta("publishnotready", ns)
- svcPublishNotReady.Spec.PublishNotReadyAddresses = true
- readyPod := newPod(1, ns, true, 1)
- readyPodHostname := newPod(1, ns, true, 1)
- readyPodHostname.Spec.Subdomain = svc.Name
- readyPodHostname.Spec.Hostname = "example-hostname"
- unreadyPod := newPod(1, ns, false, 1)
- multiIPPod := newPod(1, ns, true, 1)
- multiIPPod.Status.PodIPs = []v1.PodIP{{IP: "1.2.3.4"}, {IP: "1234::5678:0000:0000:9abc:def0"}}
- node1 := &v1.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: readyPod.Spec.NodeName,
- Labels: map[string]string{
- "topology.kubernetes.io/zone": "us-central1-a",
- "topology.kubernetes.io/region": "us-central1",
- },
- },
- }
- testCases := []struct {
- name string
- pod *v1.Pod
- node *v1.Node
- svc *v1.Service
- expectedEndpoint discovery.Endpoint
- publishNotReadyAddresses bool
- }{
- {
- name: "Ready pod",
- pod: readyPod,
- svc: &svc,
- expectedEndpoint: discovery.Endpoint{
- Addresses: []string{"1.2.3.5"},
- Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
- Topology: map[string]string{"kubernetes.io/hostname": "node-1"},
- TargetRef: &v1.ObjectReference{
- Kind: "Pod",
- Namespace: ns,
- Name: readyPod.Name,
- UID: readyPod.UID,
- ResourceVersion: readyPod.ResourceVersion,
- },
- },
- },
- {
- name: "Ready pod + publishNotReadyAddresses",
- pod: readyPod,
- svc: &svcPublishNotReady,
- expectedEndpoint: discovery.Endpoint{
- Addresses: []string{"1.2.3.5"},
- Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
- Topology: map[string]string{"kubernetes.io/hostname": "node-1"},
- TargetRef: &v1.ObjectReference{
- Kind: "Pod",
- Namespace: ns,
- Name: readyPod.Name,
- UID: readyPod.UID,
- ResourceVersion: readyPod.ResourceVersion,
- },
- },
- },
- {
- name: "Unready pod",
- pod: unreadyPod,
- svc: &svc,
- expectedEndpoint: discovery.Endpoint{
- Addresses: []string{"1.2.3.5"},
- Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(false)},
- Topology: map[string]string{"kubernetes.io/hostname": "node-1"},
- TargetRef: &v1.ObjectReference{
- Kind: "Pod",
- Namespace: ns,
- Name: readyPod.Name,
- UID: readyPod.UID,
- ResourceVersion: readyPod.ResourceVersion,
- },
- },
- },
- {
- name: "Unready pod + publishNotReadyAddresses",
- pod: unreadyPod,
- svc: &svcPublishNotReady,
- expectedEndpoint: discovery.Endpoint{
- Addresses: []string{"1.2.3.5"},
- Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
- Topology: map[string]string{"kubernetes.io/hostname": "node-1"},
- TargetRef: &v1.ObjectReference{
- Kind: "Pod",
- Namespace: ns,
- Name: readyPod.Name,
- UID: readyPod.UID,
- ResourceVersion: readyPod.ResourceVersion,
- },
- },
- },
- {
- name: "Ready pod + node labels",
- pod: readyPod,
- node: node1,
- svc: &svc,
- expectedEndpoint: discovery.Endpoint{
- Addresses: []string{"1.2.3.5"},
- Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
- Topology: map[string]string{
- "kubernetes.io/hostname": "node-1",
- "topology.kubernetes.io/zone": "us-central1-a",
- "topology.kubernetes.io/region": "us-central1",
- },
- TargetRef: &v1.ObjectReference{
- Kind: "Pod",
- Namespace: ns,
- Name: readyPod.Name,
- UID: readyPod.UID,
- ResourceVersion: readyPod.ResourceVersion,
- },
- },
- },
- {
- name: "Multi IP Ready pod + node labels",
- pod: multiIPPod,
- node: node1,
- svc: &svc,
- expectedEndpoint: discovery.Endpoint{
- Addresses: []string{"1.2.3.4"},
- Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
- Topology: map[string]string{
- "kubernetes.io/hostname": "node-1",
- "topology.kubernetes.io/zone": "us-central1-a",
- "topology.kubernetes.io/region": "us-central1",
- },
- TargetRef: &v1.ObjectReference{
- Kind: "Pod",
- Namespace: ns,
- Name: readyPod.Name,
- UID: readyPod.UID,
- ResourceVersion: readyPod.ResourceVersion,
- },
- },
- },
- {
- name: "Ready pod + hostname",
- pod: readyPodHostname,
- node: node1,
- svc: &svc,
- expectedEndpoint: discovery.Endpoint{
- Addresses: []string{"1.2.3.5"},
- Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
- Hostname: &readyPodHostname.Spec.Hostname,
- Topology: map[string]string{
- "kubernetes.io/hostname": "node-1",
- "topology.kubernetes.io/zone": "us-central1-a",
- "topology.kubernetes.io/region": "us-central1",
- },
- TargetRef: &v1.ObjectReference{
- Kind: "Pod",
- Namespace: ns,
- Name: readyPodHostname.Name,
- UID: readyPodHostname.UID,
- ResourceVersion: readyPodHostname.ResourceVersion,
- },
- },
- },
- }
- for _, testCase := range testCases {
- t.Run(testCase.name, func(t *testing.T) {
- endpoint := podToEndpoint(testCase.pod, testCase.node, testCase.svc)
- if !reflect.DeepEqual(testCase.expectedEndpoint, endpoint) {
- t.Errorf("Expected endpoint: %v, got: %v", testCase.expectedEndpoint, endpoint)
- }
- })
- }
- }
- func TestPodChangedWithPodEndpointChanged(t *testing.T) {
- podStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
- ns := "test"
- podStore.Add(newPod(1, ns, true, 1))
- pods := podStore.List()
- if len(pods) != 1 {
- t.Errorf("podStore size: expected: %d, got: %d", 1, len(pods))
- return
- }
- oldPod := pods[0].(*v1.Pod)
- newPod := oldPod.DeepCopy()
- if podChangedHelper(oldPod, newPod, podEndpointChanged) {
- t.Errorf("Expected pod to be unchanged for copied pod")
- }
- newPod.Spec.NodeName = "changed"
- if !podChangedHelper(oldPod, newPod, podEndpointChanged) {
- t.Errorf("Expected pod to be changed for pod with NodeName changed")
- }
- newPod.Spec.NodeName = oldPod.Spec.NodeName
- newPod.ObjectMeta.ResourceVersion = "changed"
- if podChangedHelper(oldPod, newPod, podEndpointChanged) {
- t.Errorf("Expected pod to be unchanged for pod with only ResourceVersion changed")
- }
- newPod.ObjectMeta.ResourceVersion = oldPod.ObjectMeta.ResourceVersion
- newPod.Status.PodIPs = []v1.PodIP{{IP: "1.2.3.1"}}
- if !podChangedHelper(oldPod, newPod, podEndpointChanged) {
- t.Errorf("Expected pod to be changed with pod IP address change")
- }
- newPod.Status.PodIPs = oldPod.Status.PodIPs
- newPod.ObjectMeta.Name = "wrong-name"
- if !podChangedHelper(oldPod, newPod, podEndpointChanged) {
- t.Errorf("Expected pod to be changed with pod name change")
- }
- newPod.ObjectMeta.Name = oldPod.ObjectMeta.Name
- saveConditions := oldPod.Status.Conditions
- oldPod.Status.Conditions = nil
- if !podChangedHelper(oldPod, newPod, podEndpointChanged) {
- t.Errorf("Expected pod to be changed with pod readiness change")
- }
- oldPod.Status.Conditions = saveConditions
- now := metav1.NewTime(time.Now().UTC())
- newPod.ObjectMeta.DeletionTimestamp = &now
- if !podChangedHelper(oldPod, newPod, podEndpointChanged) {
- t.Errorf("Expected pod to be changed with DeletionTimestamp change")
- }
- newPod.ObjectMeta.DeletionTimestamp = oldPod.ObjectMeta.DeletionTimestamp.DeepCopy()
- }
- func TestServiceControllerKey(t *testing.T) {
- testCases := map[string]struct {
- endpointSlice *discovery.EndpointSlice
- expectedKey string
- expectedErr error
- }{
- "nil EndpointSlice": {
- endpointSlice: nil,
- expectedKey: "",
- expectedErr: fmt.Errorf("nil EndpointSlice passed to serviceControllerKey()"),
- },
- "empty EndpointSlice": {
- endpointSlice: &discovery.EndpointSlice{},
- expectedKey: "",
- expectedErr: fmt.Errorf("EndpointSlice missing kubernetes.io/service-name label"),
- },
- "valid EndpointSlice": {
- endpointSlice: &discovery.EndpointSlice{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: "ns",
- Labels: map[string]string{
- discovery.LabelServiceName: "svc",
- },
- },
- },
- expectedKey: "ns/svc",
- expectedErr: nil,
- },
- }
- for name, tc := range testCases {
- t.Run(name, func(t *testing.T) {
- actualKey, actualErr := serviceControllerKey(tc.endpointSlice)
- if !reflect.DeepEqual(actualErr, tc.expectedErr) {
- t.Errorf("Expected %s, got %s", tc.expectedErr, actualErr)
- }
- if actualKey != tc.expectedKey {
- t.Errorf("Expected %s, got %s", tc.expectedKey, actualKey)
- }
- })
- }
- }
- // Test helpers
- func newPod(n int, namespace string, ready bool, nPorts int) *v1.Pod {
- status := v1.ConditionTrue
- if !ready {
- status = v1.ConditionFalse
- }
- p := &v1.Pod{
- TypeMeta: metav1.TypeMeta{APIVersion: "v1"},
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace,
- Name: fmt.Sprintf("pod%d", n),
- Labels: map[string]string{"foo": "bar"},
- },
- Spec: v1.PodSpec{
- Containers: []v1.Container{{
- Name: "container-1",
- }},
- NodeName: "node-1",
- },
- Status: v1.PodStatus{
- PodIP: fmt.Sprintf("1.2.3.%d", 4+n),
- PodIPs: []v1.PodIP{{
- IP: fmt.Sprintf("1.2.3.%d", 4+n),
- }},
- Conditions: []v1.PodCondition{
- {
- Type: v1.PodReady,
- Status: status,
- },
- },
- },
- }
- return p
- }
- func newClientset() *fake.Clientset {
- client := fake.NewSimpleClientset()
- client.PrependReactor("create", "endpointslices", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
- endpointSlice := action.(k8stesting.CreateAction).GetObject().(*discovery.EndpointSlice)
- if endpointSlice.ObjectMeta.GenerateName != "" {
- endpointSlice.ObjectMeta.Name = fmt.Sprintf("%s-%s", endpointSlice.ObjectMeta.GenerateName, rand.String(8))
- endpointSlice.ObjectMeta.GenerateName = ""
- }
- endpointSlice.ObjectMeta.ResourceVersion = "100"
- return false, endpointSlice, nil
- }))
- client.PrependReactor("update", "endpointslices", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
- endpointSlice := action.(k8stesting.CreateAction).GetObject().(*discovery.EndpointSlice)
- endpointSlice.ObjectMeta.ResourceVersion = "200"
- return false, endpointSlice, nil
- }))
- return client
- }
- func newServiceAndEndpointMeta(name, namespace string) (v1.Service, endpointMeta) {
- portNum := int32(80)
- portNameIntStr := intstr.IntOrString{
- Type: intstr.Int,
- IntVal: portNum,
- }
- svc := v1.Service{
- ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
- Spec: v1.ServiceSpec{
- Ports: []v1.ServicePort{{
- TargetPort: portNameIntStr,
- Protocol: v1.ProtocolTCP,
- Name: name,
- }},
- Selector: map[string]string{"foo": "bar"},
- },
- }
- addressType := discovery.AddressTypeIPv4
- protocol := v1.ProtocolTCP
- endpointMeta := endpointMeta{
- AddressType: addressType,
- Ports: []discovery.EndpointPort{{Name: &name, Port: &portNum, Protocol: &protocol}},
- }
- return svc, endpointMeta
- }
- func newEmptyEndpointSlice(n int, namespace string, endpointMeta endpointMeta, svc v1.Service) *discovery.EndpointSlice {
- return &discovery.EndpointSlice{
- ObjectMeta: metav1.ObjectMeta{
- Name: fmt.Sprintf("%s.%d", svc.Name, n),
- Namespace: namespace,
- },
- Ports: endpointMeta.Ports,
- AddressType: endpointMeta.AddressType,
- Endpoints: []discovery.Endpoint{},
- }
- }
- func podChangedHelper(oldPod, newPod *v1.Pod, endpointChanged endpointutil.EndpointsMatch) bool {
- podChanged, _ := endpointutil.PodChanged(oldPod, newPod, podEndpointChanged)
- return podChanged
- }
|