fix(source/service): disable pod and endpointSlices informers when they are not needed (#5646)

* fix(source/service): disable pod and endpointSlicesInformer when not required

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* fix(source/service): disable pod and endpointSlicesInformer when not required

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* fix(source/service): disable pod and endpointSlices informers when they are not needed

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* fix(source/service): disable pod and endpointSlices informers when they are not needed

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

---------

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
This commit is contained in:
Ivan Ka 2025-07-30 09:54:28 +01:00 committed by GitHub
parent 0d1309c7fa
commit 1b9d7cddc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 273 additions and 69 deletions

60
source/informers/fake.go Normal file
View File

@ -0,0 +1,60 @@
/*
Copyright 2025 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 informers
import (
"github.com/stretchr/testify/mock"
corev1lister "k8s.io/client-go/listers/core/v1"
discoveryv1lister "k8s.io/client-go/listers/discovery/v1"
"k8s.io/client-go/tools/cache"
)
type FakeServiceInformer struct {
mock.Mock
}
func (f *FakeServiceInformer) Informer() cache.SharedIndexInformer {
args := f.Called()
return args.Get(0).(cache.SharedIndexInformer)
}
func (f *FakeServiceInformer) Lister() corev1lister.ServiceLister {
return corev1lister.NewServiceLister(f.Informer().GetIndexer())
}
type FakeEndpointSliceInformer struct {
mock.Mock
}
func (f *FakeEndpointSliceInformer) Informer() cache.SharedIndexInformer {
args := f.Called()
return args.Get(0).(cache.SharedIndexInformer)
}
func (f *FakeEndpointSliceInformer) Lister() discoveryv1lister.EndpointSliceLister {
return discoveryv1lister.NewEndpointSliceLister(f.Informer().GetIndexer())
}
type FakeNodeInformer struct {
mock.Mock
}
func (f *FakeNodeInformer) Informer() cache.SharedIndexInformer {
args := f.Called()
return args.Get(0).(cache.SharedIndexInformer)
}
func (f *FakeNodeInformer) Lister() corev1lister.NodeLister {
return corev1lister.NewNodeLister(f.Informer().GetIndexer())
}

View File

@ -96,28 +96,9 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name
// Set the resync period to 0 to prevent processing when nothing has changed
informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace))
serviceInformer := informerFactory.Core().V1().Services()
endpointSlicesInformer := informerFactory.Discovery().V1().EndpointSlices()
podInformer := informerFactory.Core().V1().Pods()
// Add default resource event handlers to properly initialize informer.
_, _ = serviceInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
},
},
)
_, _ = endpointSlicesInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
},
},
)
_, _ = podInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
},
},
)
_, _ = serviceInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
// Transform the slice into a map so it will be way much easier and fast to filter later
sTypesFilter, err := newServiceTypesFilter(serviceTypeFilter)
@ -125,30 +106,40 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name
return nil, err
}
var nodeInformer coreinformers.NodeInformer
if sTypesFilter.isNodeInformerRequired() {
nodeInformer = informerFactory.Core().V1().Nodes()
_, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
var endpointSlicesInformer discoveryinformers.EndpointSliceInformer
var podInformer coreinformers.PodInformer
if sTypesFilter.isRequired(v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP) {
endpointSlicesInformer = informerFactory.Discovery().V1().EndpointSlices()
podInformer = informerFactory.Core().V1().Pods()
_, _ = endpointSlicesInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
_, _ = podInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
// Add an indexer to the EndpointSlice informer to index by the service name label
err = endpointSlicesInformer.Informer().AddIndexers(cache.Indexers{
serviceNameIndexKey: func(obj any) ([]string, error) {
endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
if !ok {
// This should never happen because the Informer should only contain EndpointSlice objects
return nil, fmt.Errorf("expected %T but got %T instead", endpointSlice, obj)
}
serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName]
if serviceName == "" {
return nil, nil
}
key := types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}.String()
return []string{key}, nil
},
})
if err != nil {
return nil, err
}
}
// Add an indexer to the EndpointSlice informer to index by the service name label
err = endpointSlicesInformer.Informer().AddIndexers(cache.Indexers{
serviceNameIndexKey: func(obj any) ([]string, error) {
endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
if !ok {
// This should never happen because the Informer should only contain EndpointSlice objects
return nil, fmt.Errorf("expected %T but got %T instead", endpointSlice, obj)
}
serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName]
if serviceName == "" {
return nil, nil
}
key := types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}.String()
return []string{key}, nil
},
})
if err != nil {
return nil, err
var nodeInformer coreinformers.NodeInformer
if sTypesFilter.isRequired(v1.ServiceTypeNodePort) {
nodeInformer = informerFactory.Core().V1().Nodes()
_, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
}
informerFactory.Start(ctx.Done())
@ -808,10 +799,10 @@ func (sc *serviceSource) AddEventHandler(_ context.Context, handler func()) {
// Right now there is no way to remove event handler from informer, see:
// https://github.com/kubernetes/kubernetes/issues/79610
_, _ = sc.serviceInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
if sc.listenEndpointEvents {
if sc.listenEndpointEvents && sc.serviceTypeFilter.isRequired(v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP) {
_, _ = sc.endpointSlicesInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
}
if sc.serviceTypeFilter.isNodeInformerRequired() {
if sc.serviceTypeFilter.isRequired(v1.ServiceTypeNodePort) {
_, _ = sc.nodeInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
}
}
@ -848,12 +839,18 @@ func (sc *serviceTypes) isProcessed(serviceType v1.ServiceType) bool {
return !sc.enabled || sc.types[serviceType]
}
func (sc *serviceTypes) isNodeInformerRequired() bool {
if !sc.enabled {
// isRequired returns true if service type filtering is disabled or if any of the provided service types are present in the filter.
// If no options are provided, it returns true.
func (sc *serviceTypes) isRequired(opts ...v1.ServiceType) bool {
if len(opts) == 0 || !sc.enabled {
return true
}
_, ok := sc.types[v1.ServiceTypeNodePort]
return ok
for _, opt := range opts {
if _, ok := sc.types[opt]; ok {
return true
}
}
return false
}
// conditionToBool converts an EndpointConditions condition to a bool value.

View File

@ -37,6 +37,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes/fake"
"sigs.k8s.io/external-dns/source/informers"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/internal/testutils"
@ -251,7 +252,7 @@ func testServiceSourceEndpoints(t *testing.T) {
},
externalIPs: []string{},
lbs: []string{"1.2.3.4"},
serviceTypesFilter: []string{},
serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)},
expected: []*endpoint.Endpoint{
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
@ -296,7 +297,7 @@ func testServiceSourceEndpoints(t *testing.T) {
annotations: map[string]string{},
externalIPs: []string{},
lbs: []string{"1.2.3.4"},
serviceTypesFilter: []string{},
serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)},
expected: []*endpoint.Endpoint{
{DNSName: "foo.fqdn.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "foo.fqdn.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
@ -2498,6 +2499,7 @@ func TestHeadlessServices(t *testing.T) {
podsReady []bool
publishNotReadyAddresses bool
nodes []v1.Node
serviceTypesFilter []string
expected []*endpoint.Endpoint
expectError bool
}{
@ -2528,6 +2530,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}},
@ -2562,6 +2565,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true},
false,
[]v1.Node{},
[]string{string(v1.ServiceTypeClusterIP), string(v1.ServiceTypeLoadBalancer)},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}},
@ -2596,6 +2600,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{},
false,
},
@ -2627,6 +2632,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)},
@ -2662,6 +2668,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
@ -2696,6 +2703,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, false},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
@ -2729,6 +2737,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, false},
true,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}},
@ -2763,6 +2772,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
},
@ -2795,6 +2805,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
},
@ -2827,6 +2838,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
@ -2861,6 +2873,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true, true},
false,
[]v1.Node{},
[]string{string(v1.ServiceTypeClusterIP)},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
@ -2895,6 +2908,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
@ -2939,6 +2953,7 @@ func TestHeadlessServices(t *testing.T) {
},
},
},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
@ -2987,6 +3002,7 @@ func TestHeadlessServices(t *testing.T) {
},
},
},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::5"}},
},
@ -3031,6 +3047,7 @@ func TestHeadlessServices(t *testing.T) {
},
},
},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
@ -3079,6 +3096,7 @@ func TestHeadlessServices(t *testing.T) {
},
},
},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
@ -3113,6 +3131,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
@ -3146,6 +3165,7 @@ func TestHeadlessServices(t *testing.T) {
[]bool{true, true, true},
false,
[]v1.Node{},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
@ -3242,7 +3262,7 @@ func TestHeadlessServices(t *testing.T) {
true,
false,
false,
[]string{},
tc.serviceTypesFilter,
tc.ignoreHostnameAnnotation,
labels.Everything(),
false,
@ -3994,7 +4014,6 @@ func TestHeadlessServicesHostIP(t *testing.T) {
t.Run(tc.title, func(t *testing.T) {
t.Parallel()
// Create a Kubernetes testing client
kubernetes := fake.NewClientset()
service := &v1.Service{
@ -4134,7 +4153,7 @@ func TestExternalServices(t *testing.T) {
},
"111.111.111.111",
[]string{},
[]string{},
[]string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", Targets: endpoint.Targets{"111.111.111.111"}, RecordType: endpoint.RecordTypeA},
},
@ -4176,7 +4195,7 @@ func TestExternalServices(t *testing.T) {
},
"remote.example.com",
[]string{},
[]string{},
[]string{string(v1.ServiceTypeExternalName)},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", Targets: endpoint.Targets{"remote.example.com"}, RecordType: endpoint.RecordTypeCNAME},
},
@ -4371,6 +4390,8 @@ func TestNewServiceSourceInformersEnabled(t *testing.T) {
assert.NotNil(t, svc.serviceTypeFilter)
assert.False(t, svc.serviceTypeFilter.enabled)
assert.NotNil(t, svc.nodeInformer)
assert.NotNil(t, svc.serviceInformer)
assert.NotNil(t, svc.endpointSlicesInformer)
},
},
{
@ -4380,17 +4401,49 @@ func TestNewServiceSourceInformersEnabled(t *testing.T) {
assert.NotNil(t, svc)
assert.NotNil(t, svc.serviceTypeFilter)
assert.True(t, svc.serviceTypeFilter.enabled)
assert.NotNil(t, svc.serviceInformer)
assert.Nil(t, svc.nodeInformer)
assert.NotNil(t, svc.endpointSlicesInformer)
assert.NotNil(t, svc.podInformer)
},
},
{
name: "serviceTypeFilter contains NodePort",
svcFilter: []string{string(v1.ServiceTypeNodePort)},
name: "serviceTypeFilter contains NodePort and ExternalName",
svcFilter: []string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)},
asserts: func(svc *serviceSource) {
assert.NotNil(t, svc)
assert.NotNil(t, svc.serviceTypeFilter)
assert.True(t, svc.serviceTypeFilter.enabled)
assert.NotNil(t, svc.serviceInformer)
assert.NotNil(t, svc.nodeInformer)
assert.NotNil(t, svc.endpointSlicesInformer)
assert.NotNil(t, svc.podInformer)
},
},
{
name: "serviceTypeFilter contains ExternalName",
svcFilter: []string{string(v1.ServiceTypeExternalName)},
asserts: func(svc *serviceSource) {
assert.NotNil(t, svc)
assert.NotNil(t, svc.serviceTypeFilter)
assert.True(t, svc.serviceTypeFilter.enabled)
assert.NotNil(t, svc.serviceInformer)
assert.Nil(t, svc.nodeInformer)
assert.Nil(t, svc.endpointSlicesInformer)
assert.Nil(t, svc.podInformer)
},
},
{
name: "serviceTypeFilter contains LoadBalancer",
svcFilter: []string{string(v1.ServiceTypeLoadBalancer)},
asserts: func(svc *serviceSource) {
assert.NotNil(t, svc)
assert.NotNil(t, svc.serviceTypeFilter)
assert.True(t, svc.serviceTypeFilter.enabled)
assert.NotNil(t, svc.serviceInformer)
assert.Nil(t, svc.nodeInformer)
assert.Nil(t, svc.endpointSlicesInformer)
assert.Nil(t, svc.podInformer)
},
},
}
@ -4681,32 +4734,126 @@ func createTestServicesByType(namespace string, typeCounts map[v1.ServiceType]in
func TestServiceTypes_isNodeInformerRequired(t *testing.T) {
tests := []struct {
name string
filter []string
want bool
name string
filter []string
required []v1.ServiceType
want bool
}{
{
name: "NodePort type present",
filter: []string{string(v1.ServiceTypeNodePort)},
want: true,
name: "NodePort required and filter is empty",
filter: []string{},
required: []v1.ServiceType{v1.ServiceTypeNodePort},
want: true,
},
{
name: "NodePort type absent, filter enabled",
filter: []string{string(v1.ServiceTypeLoadBalancer)},
want: false,
name: "NodePort type present",
filter: []string{string(v1.ServiceTypeNodePort)},
required: []v1.ServiceType{v1.ServiceTypeNodePort},
want: true,
},
{
name: "NodePort and other filters present",
filter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)},
want: true,
name: "NodePort type absent, filter enabled",
filter: []string{string(v1.ServiceTypeLoadBalancer)},
required: []v1.ServiceType{v1.ServiceTypeNodePort},
want: false,
},
{
name: "NodePort and other filters present",
filter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)},
required: []v1.ServiceType{v1.ServiceTypeNodePort},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter, _ := newServiceTypesFilter(tt.filter)
got := filter.isNodeInformerRequired()
got := filter.isRequired(tt.required...)
assert.Equal(t, tt.want, got)
})
}
}
func TestServiceSource_AddEventHandler(t *testing.T) {
var fakeServiceInformer *informers.FakeServiceInformer
var fakeEdpInformer *informers.FakeEndpointSliceInformer
var fakeNodeInformer *informers.FakeNodeInformer
tests := []struct {
name string
filter []string
times int
asserts func(t *testing.T, s *serviceSource)
}{
{
name: "AddEventHandler should trigger all event handlers when empty filter is provided",
filter: []string{},
times: 3,
asserts: func(t *testing.T, s *serviceSource) {
fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1)
fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 1)
},
},
{
name: "AddEventHandler should trigger only service event handler",
filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer)},
times: 1,
asserts: func(t *testing.T, s *serviceSource) {
fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 0)
fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0)
},
},
{
name: "AddEventHandler should configure only service event handler",
filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeClusterIP)},
times: 2,
asserts: func(t *testing.T, s *serviceSource) {
fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1)
fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0)
},
},
{
name: "AddEventHandler should configure all service event handlers",
filter: []string{string(v1.ServiceTypeNodePort)},
times: 3,
asserts: func(t *testing.T, s *serviceSource) {
fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1)
fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 1)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeServiceInformer = new(informers.FakeServiceInformer)
infSvc := testInformer{}
fakeServiceInformer.On("Informer").Return(&infSvc)
fakeEdpInformer = new(informers.FakeEndpointSliceInformer)
infEdp := testInformer{}
fakeEdpInformer.On("Informer").Return(&infEdp)
fakeNodeInformer = new(informers.FakeNodeInformer)
infNode := testInformer{}
fakeNodeInformer.On("Informer").Return(&infNode)
filter, _ := newServiceTypesFilter(tt.filter)
svcSource := &serviceSource{
endpointSlicesInformer: fakeEdpInformer,
serviceInformer: fakeServiceInformer,
nodeInformer: fakeNodeInformer,
serviceTypeFilter: filter,
listenEndpointEvents: true,
}
svcSource.AddEventHandler(t.Context(), func() {})
assert.Equal(t, tt.times, infSvc.times+infEdp.times+infNode.times)
tt.asserts(t, svcSource)
})
}
}