2014-06-06 19:40:48 -04:00
/ *
2016-06-02 20:25:58 -04:00
Copyright 2014 The Kubernetes Authors .
2014-06-06 19:40:48 -04:00
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 .
* /
2014-06-23 14:32:11 -04:00
2015-10-09 23:58:57 -04:00
package endpoint
2014-06-06 19:40:48 -04:00
import (
2021-03-08 20:54:18 -05:00
"context"
2014-06-06 19:40:48 -04:00
"fmt"
2014-08-28 00:32:52 -04:00
"net/http"
2014-07-18 16:22:26 -04:00
"net/http/httptest"
2019-11-15 15:30:42 -05:00
"reflect"
2019-07-24 05:01:42 -04:00
"strconv"
2025-02-11 17:43:17 -05:00
"strings"
2014-06-06 19:40:48 -04:00
"testing"
2017-06-19 11:47:29 -04:00
"time"
2014-06-06 19:40:48 -04:00
2023-03-23 14:34:03 -04:00
"github.com/google/go-cmp/cmp"
2024-08-19 10:38:16 -04:00
2019-07-24 05:01:42 -04:00
v1 "k8s.io/api/core/v1"
2017-01-11 09:09:48 -05:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
2018-05-01 10:54:37 -04:00
"k8s.io/apimachinery/pkg/runtime/schema"
2017-01-27 15:42:17 -05:00
"k8s.io/apimachinery/pkg/util/intstr"
2017-06-19 11:47:29 -04:00
"k8s.io/apimachinery/pkg/util/wait"
2017-06-23 16:56:37 -04:00
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
2021-03-08 20:54:18 -05:00
"k8s.io/client-go/kubernetes/fake"
2019-12-13 11:28:11 -05:00
clientscheme "k8s.io/client-go/kubernetes/scheme"
2017-01-19 13:27:59 -05:00
restclient "k8s.io/client-go/rest"
2017-01-24 09:11:51 -05:00
"k8s.io/client-go/tools/cache"
2017-01-23 13:37:22 -05:00
utiltesting "k8s.io/client-go/util/testing"
2016-11-18 15:50:17 -05:00
endptspkg "k8s.io/kubernetes/pkg/api/v1/endpoints"
2017-11-08 17:34:54 -05:00
api "k8s.io/kubernetes/pkg/apis/core"
2020-10-01 03:08:48 -04:00
controllerpkg "k8s.io/kubernetes/pkg/controller"
2023-12-13 03:11:08 -05:00
"k8s.io/kubernetes/test/utils/ktesting"
2020-05-24 18:27:20 -04:00
utilnet "k8s.io/utils/net"
2025-07-07 07:22:36 -04:00
"k8s.io/utils/ptr"
2014-06-06 19:40:48 -04:00
)
2016-03-16 04:47:30 -04:00
var alwaysReady = func ( ) bool { return true }
2017-06-19 11:47:29 -04:00
var neverReady = func ( ) bool { return false }
2016-08-15 18:25:59 -04:00
var emptyNodeName string
2019-01-24 10:37:58 -05:00
var triggerTime = time . Date ( 2018 , 01 , 01 , 0 , 0 , 0 , 0 , time . UTC )
var triggerTimeString = triggerTime . Format ( time . RFC3339Nano )
var oldTriggerTimeString = triggerTime . Add ( - time . Hour ) . Format ( time . RFC3339Nano )
2016-03-16 04:47:30 -04:00
2020-05-24 18:27:20 -04:00
var ipv4only = [ ] v1 . IPFamily { v1 . IPv4Protocol }
var ipv6only = [ ] v1 . IPFamily { v1 . IPv6Protocol }
var ipv4ipv6 = [ ] v1 . IPFamily { v1 . IPv4Protocol , v1 . IPv6Protocol }
var ipv6ipv4 = [ ] v1 . IPFamily { v1 . IPv6Protocol , v1 . IPv4Protocol }
func testPod ( namespace string , id int , nPorts int , isReady bool , ipFamilies [ ] v1 . IPFamily ) * v1 . Pod {
2019-07-24 05:01:42 -04:00
p := & v1 . Pod {
TypeMeta : metav1 . TypeMeta { APIVersion : "v1" } ,
ObjectMeta : metav1 . ObjectMeta {
2022-02-17 01:10:49 -05:00
Namespace : namespace ,
Name : fmt . Sprintf ( "pod%d" , id ) ,
Labels : map [ string ] string { "foo" : "bar" } ,
ResourceVersion : fmt . Sprint ( id ) ,
2019-07-24 05:01:42 -04:00
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container { { Ports : [ ] v1 . ContainerPort { } } } ,
} ,
Status : v1 . PodStatus {
Conditions : [ ] v1 . PodCondition {
{
Type : v1 . PodReady ,
Status : v1 . ConditionTrue ,
2015-02-02 13:51:52 -05:00
} ,
2014-07-18 16:22:26 -04:00
} ,
2019-07-24 05:01:42 -04:00
} ,
}
if ! isReady {
p . Status . Conditions [ 0 ] . Status = v1 . ConditionFalse
}
for j := 0 ; j < nPorts ; j ++ {
p . Spec . Containers [ 0 ] . Ports = append ( p . Spec . Containers [ 0 ] . Ports ,
v1 . ContainerPort { Name : fmt . Sprintf ( "port%d" , j ) , ContainerPort : int32 ( 8080 + j ) } )
}
2020-05-24 18:27:20 -04:00
for _ , family := range ipFamilies {
var ip string
if family == v1 . IPv4Protocol {
2025-02-11 17:43:17 -05:00
// if id=1, ip=1.2.3.4
// if id=2, ip=1.2.3.5
// if id=999, ip=1.2.6.235
ip = fmt . Sprintf ( "1.2.%d.%d" , 3 + ( ( 4 + id ) / 256 ) , ( 4 + id ) % 256 )
2020-05-24 18:27:20 -04:00
} else {
2025-02-11 17:43:17 -05:00
ip = fmt . Sprintf ( "2000::%x" , 4 + id )
2020-05-24 18:27:20 -04:00
}
p . Status . PodIPs = append ( p . Status . PodIPs , v1 . PodIP { IP : ip } )
2019-08-19 16:45:22 -04:00
}
2020-05-24 11:55:09 -04:00
p . Status . PodIP = p . Status . PodIPs [ 0 ] . IP
2019-07-24 05:01:42 -04:00
return p
}
2020-05-24 18:27:20 -04:00
func addPods ( store cache . Store , namespace string , nPods int , nPorts int , nNotReady int , ipFamilies [ ] v1 . IPFamily ) {
2019-07-24 05:01:42 -04:00
for i := 0 ; i < nPods + nNotReady ; i ++ {
isReady := i < nPods
2020-05-24 18:27:20 -04:00
pod := testPod ( namespace , i , nPorts , isReady , ipFamilies )
2019-07-24 05:01:42 -04:00
store . Add ( pod )
2014-07-18 16:22:26 -04:00
}
}
2025-02-11 17:43:17 -05:00
func addBadIPPod ( store cache . Store , namespace string , ipFamilies [ ] v1 . IPFamily ) {
pod := testPod ( namespace , 0 , 1 , true , ipFamilies )
pod . Status . PodIPs [ 0 ] . IP = "0" + pod . Status . PodIPs [ 0 ] . IP
pod . Status . PodIP = pod . Status . PodIPs [ 0 ] . IP
store . Add ( pod )
}
2017-06-14 03:54:33 -04:00
func addNotReadyPodsWithSpecifiedRestartPolicyAndPhase ( store cache . Store , namespace string , nPods int , nPorts int , restartPolicy v1 . RestartPolicy , podPhase v1 . PodPhase ) {
for i := 0 ; i < nPods ; i ++ {
p := & v1 . Pod {
2018-05-01 10:54:37 -04:00
TypeMeta : metav1 . TypeMeta { APIVersion : "v1" } ,
2017-06-14 03:54:33 -04:00
ObjectMeta : metav1 . ObjectMeta {
Namespace : namespace ,
Name : fmt . Sprintf ( "pod%d" , i ) ,
Labels : map [ string ] string { "foo" : "bar" } ,
} ,
Spec : v1 . PodSpec {
RestartPolicy : restartPolicy ,
Containers : [ ] v1 . Container { { Ports : [ ] v1 . ContainerPort { } } } ,
} ,
Status : v1 . PodStatus {
PodIP : fmt . Sprintf ( "1.2.3.%d" , 4 + i ) ,
Phase : podPhase ,
Conditions : [ ] v1 . PodCondition {
{
Type : v1 . PodReady ,
Status : v1 . ConditionFalse ,
} ,
} ,
} ,
}
for j := 0 ; j < nPorts ; j ++ {
p . Spec . Containers [ 0 ] . Ports = append ( p . Spec . Containers [ 0 ] . Ports ,
2019-07-24 05:01:42 -04:00
v1 . ContainerPort { Name : fmt . Sprintf ( "port%d" , j ) , ContainerPort : int32 ( 8080 + j ) } )
2017-06-14 03:54:33 -04:00
}
store . Add ( p )
}
}
2017-06-19 11:47:29 -04:00
func makeTestServer ( t * testing . T , namespace string ) ( * httptest . Server , * utiltesting . FakeHandler ) {
2016-01-15 01:33:50 -05:00
fakeEndpointsHandler := utiltesting . FakeHandler {
2017-06-19 11:47:29 -04:00
StatusCode : http . StatusOK ,
2019-12-13 11:28:11 -05:00
ResponseBody : runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints { } ) ,
2014-09-26 16:34:55 -04:00
}
2014-08-28 00:32:52 -04:00
mux := http . NewServeMux ( )
2019-12-13 11:28:11 -05:00
if namespace == "" {
t . Fatal ( "namespace cannot be empty" )
}
mux . Handle ( "/api/v1/namespaces/" + namespace + "/endpoints" , & fakeEndpointsHandler )
mux . Handle ( "/api/v1/namespaces/" + namespace + "/endpoints/" , & fakeEndpointsHandler )
2014-08-28 00:32:52 -04:00
mux . HandleFunc ( "/" , func ( res http . ResponseWriter , req * http . Request ) {
t . Errorf ( "unexpected request: %v" , req . RequestURI )
2018-10-08 15:20:52 -04:00
http . Error ( res , "" , http . StatusNotFound )
2014-08-28 00:32:52 -04:00
} )
2014-09-26 16:34:55 -04:00
return httptest . NewServer ( mux ) , & fakeEndpointsHandler
2014-08-28 00:32:52 -04:00
}
2024-06-24 13:57:06 -04:00
// makeBlockingEndpointTestServer will signal the blockNextAction channel on endpoint "POST", "PUT", and "DELETE"
// requests. "POST" and "PUT" requests will wait on a blockUpdate signal if provided, while "DELETE" requests will wait
// on a blockDelete signal if provided. If controller is nil, an error will be sent in the response.
func makeBlockingEndpointTestServer ( t * testing . T , controller * endpointController , endpoint * v1 . Endpoints , blockUpdate , blockDelete , blockNextAction chan struct { } , namespace string ) * httptest . Server {
2020-07-20 20:22:54 -04:00
handlerFunc := func ( res http . ResponseWriter , req * http . Request ) {
if controller == nil {
res . WriteHeader ( http . StatusInternalServerError )
res . Write ( [ ] byte ( "controller has not been set yet" ) )
return
}
2024-06-24 13:57:06 -04:00
if req . Method == "POST" || req . Method == "PUT" {
if blockUpdate != nil {
go func ( ) {
// Delay the update of endpoints to make endpoints cache out of sync
<- blockUpdate
_ = controller . endpointsStore . Add ( endpoint )
} ( )
} else {
_ = controller . endpointsStore . Add ( endpoint )
}
2020-07-20 20:22:54 -04:00
blockNextAction <- struct { } { }
}
if req . Method == "DELETE" {
2024-06-24 13:57:06 -04:00
if blockDelete != nil {
go func ( ) {
// Delay the deletion of endpoints to make endpoints cache out of sync
<- blockDelete
_ = controller . endpointsStore . Delete ( endpoint )
controller . onEndpointsDelete ( endpoint )
} ( )
} else {
_ = controller . endpointsStore . Delete ( endpoint )
2020-07-20 20:22:54 -04:00
controller . onEndpointsDelete ( endpoint )
2024-06-24 13:57:06 -04:00
}
2020-07-20 20:22:54 -04:00
blockNextAction <- struct { } { }
}
2024-06-24 13:57:06 -04:00
res . Header ( ) . Set ( "Content-Type" , "application/json" )
2020-07-20 20:22:54 -04:00
res . WriteHeader ( http . StatusOK )
2024-06-24 13:57:06 -04:00
_ , _ = res . Write ( [ ] byte ( runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , endpoint ) ) )
2020-07-20 20:22:54 -04:00
}
mux := http . NewServeMux ( )
mux . HandleFunc ( "/api/v1/namespaces/" + namespace + "/endpoints" , handlerFunc )
mux . HandleFunc ( "/api/v1/namespaces/" + namespace + "/endpoints/" , handlerFunc )
mux . HandleFunc ( "/api/v1/namespaces/" + namespace + "/events" , func ( res http . ResponseWriter , req * http . Request ) { } )
mux . HandleFunc ( "/" , func ( res http . ResponseWriter , req * http . Request ) {
t . Errorf ( "unexpected request: %v" , req . RequestURI )
http . Error ( res , "" , http . StatusNotFound )
} )
return httptest . NewServer ( mux )
}
2017-02-07 20:25:52 -05:00
type endpointController struct {
2020-10-01 03:08:48 -04:00
* Controller
2017-06-19 11:47:29 -04:00
podStore cache . Store
serviceStore cache . Store
endpointsStore cache . Store
2017-02-07 20:25:52 -05:00
}
2023-12-13 03:11:08 -05:00
func newController ( ctx context . Context , url string , batchPeriod time . Duration ) * endpointController {
2024-08-19 10:38:16 -04:00
client := clientset . NewForConfigOrDie ( & restclient . Config { Host : url , ContentConfig : restclient . ContentConfig { GroupVersion : & schema . GroupVersion { Group : "" , Version : "v1" } , ContentType : runtime . ContentTypeJSON } } )
2020-10-01 03:08:48 -04:00
informerFactory := informers . NewSharedInformerFactory ( client , controllerpkg . NoResyncPeriodFunc ( ) )
2023-12-13 03:11:08 -05:00
endpoints := NewEndpointController ( ctx , informerFactory . Core ( ) . V1 ( ) . Pods ( ) , informerFactory . Core ( ) . V1 ( ) . Services ( ) ,
2019-07-24 05:01:42 -04:00
informerFactory . Core ( ) . V1 ( ) . Endpoints ( ) , client , batchPeriod )
2017-02-07 20:25:52 -05:00
endpoints . podsSynced = alwaysReady
endpoints . servicesSynced = alwaysReady
2017-06-19 11:47:29 -04:00
endpoints . endpointsSynced = alwaysReady
2017-02-07 20:25:52 -05:00
return & endpointController {
endpoints ,
informerFactory . Core ( ) . V1 ( ) . Pods ( ) . Informer ( ) . GetStore ( ) ,
informerFactory . Core ( ) . V1 ( ) . Services ( ) . Informer ( ) . GetStore ( ) ,
2017-06-19 11:47:29 -04:00
informerFactory . Core ( ) . V1 ( ) . Endpoints ( ) . Informer ( ) . GetStore ( ) ,
2017-02-07 20:25:52 -05:00
}
}
2023-12-13 03:11:08 -05:00
func newFakeController ( ctx context . Context , batchPeriod time . Duration ) ( * fake . Clientset , * endpointController ) {
2021-03-08 20:54:18 -05:00
client := fake . NewSimpleClientset ( )
informerFactory := informers . NewSharedInformerFactory ( client , controllerpkg . NoResyncPeriodFunc ( ) )
eController := NewEndpointController (
2023-12-13 03:11:08 -05:00
ctx ,
2021-03-08 20:54:18 -05:00
informerFactory . Core ( ) . V1 ( ) . Pods ( ) ,
informerFactory . Core ( ) . V1 ( ) . Services ( ) ,
informerFactory . Core ( ) . V1 ( ) . Endpoints ( ) ,
client ,
batchPeriod )
eController . podsSynced = alwaysReady
eController . servicesSynced = alwaysReady
eController . endpointsSynced = alwaysReady
return client , & endpointController {
eController ,
informerFactory . Core ( ) . V1 ( ) . Pods ( ) . Informer ( ) . GetStore ( ) ,
informerFactory . Core ( ) . V1 ( ) . Services ( ) . Informer ( ) . GetStore ( ) ,
informerFactory . Core ( ) . V1 ( ) . Endpoints ( ) . Informer ( ) . GetStore ( ) ,
}
}
2014-11-18 12:49:00 -05:00
func TestSyncEndpointsItemsPreserveNoSelector ( t * testing . T ) {
2017-01-21 22:36:02 -05:00
ns := metav1 . NamespaceDefault
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 } } ,
} } ,
} )
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec { Ports : [ ] v1 . ServicePort { { Port : 80 } } } ,
2015-04-16 19:18:02 -04:00
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2015-02-18 17:43:37 -05:00
endpointsHandler . ValidateRequestCount ( t , 0 )
}
2017-05-12 13:01:54 -04:00
func TestSyncEndpointsExistingNilSubsets ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-05-12 13:01:54 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2025-03-04 11:06:10 -05:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2025-03-04 11:06:10 -05:00
} ,
2017-05-12 13:01:54 -04:00
} ,
Subsets : nil ,
} )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-05-12 13:01:54 -04:00
endpointsHandler . ValidateRequestCount ( t , 0 )
}
func TestSyncEndpointsExistingEmptySubsets ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-05-12 13:01:54 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2025-03-04 11:06:10 -05:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2025-03-04 11:06:10 -05:00
} ,
2017-05-12 13:01:54 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-05-12 13:01:54 -04:00
endpointsHandler . ValidateRequestCount ( t , 0 )
}
2022-02-11 10:43:36 -05:00
func TestSyncEndpointsWithPodResourceVersionUpdateOnly ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
pod0 := testPod ( ns , 0 , 1 , true , ipv4only )
pod1 := testPod ( ns , 1 , 1 , false , ipv4only )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2022-02-11 10:43:36 -05:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2025-03-04 11:06:10 -05:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2025-03-04 11:06:10 -05:00
} ,
2022-02-11 10:43:36 -05:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress {
{
IP : pod0 . Status . PodIPs [ 0 ] . IP ,
NodeName : & emptyNodeName ,
TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : pod0 . Name , Namespace : ns , ResourceVersion : "1" } ,
} ,
} ,
NotReadyAddresses : [ ] v1 . EndpointAddress {
{
IP : pod1 . Status . PodIPs [ 0 ] . IP ,
NodeName : & emptyNodeName ,
TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : pod1 . Name , Namespace : ns , ResourceVersion : "2" } ,
} ,
} ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
} } ,
} )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2022-02-11 10:43:36 -05:00
} ,
} )
pod0 . ResourceVersion = "3"
pod1 . ResourceVersion = "4"
endpoints . podStore . Add ( pod0 )
endpoints . podStore . Add ( pod1 )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2022-02-11 10:43:36 -05:00
endpointsHandler . ValidateRequestCount ( t , 0 )
}
2017-05-12 13:01:54 -04:00
func TestSyncEndpointsNewNoSubsets ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-05-12 13:01:54 -04:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-05-12 13:01:54 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
}
2015-04-24 17:16:27 -04:00
func TestCheckLeftoverEndpoints ( t * testing . T ) {
2017-01-21 22:36:02 -05:00
ns := metav1 . NamespaceDefault
2017-06-19 11:47:29 -04:00
testServer , _ := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 } } ,
} } ,
} )
2015-04-24 17:16:27 -04:00
endpoints . checkLeftoverEndpoints ( )
if e , a := 1 , endpoints . queue . Len ( ) ; e != a {
t . Fatalf ( "Expected %v, got %v" , e , a )
}
got , _ := endpoints . queue . Get ( )
if e , a := ns + "/foo" , got ; e != a {
t . Errorf ( "Expected %v, got %v" , e , a )
}
}
2015-02-18 17:43:37 -05:00
func TestSyncEndpointsProtocolTCP ( t * testing . T ) {
2015-04-16 19:18:02 -04:00
ns := "other"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 , Protocol : "TCP" } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "TCP" } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-04-16 19:18:02 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2016-01-13 20:43:52 -05:00
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2016-01-13 20:43:52 -05:00
} ,
2016-11-18 15:50:17 -05:00
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
2016-01-13 20:43:52 -05:00
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2015-02-18 17:43:37 -05:00
}
2020-08-19 13:16:29 -04:00
func TestSyncEndpointsHeadlessServiceLabel ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2020-08-19 13:16:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2020-08-19 13:16:29 -04:00
v1 . IsHeadlessService : "" ,
} ,
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2020-08-19 13:16:29 -04:00
endpointsHandler . ValidateRequestCount ( t , 0 )
}
2023-01-18 06:12:34 -05:00
func TestSyncServiceExternalNameType ( t * testing . T ) {
serviceName := "testing-1"
namespace := metav1 . NamespaceDefault
testCases := [ ] struct {
desc string
service * v1 . Service
} {
{
desc : "External name with selector and ports should not receive endpoints" ,
service : & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : serviceName , Namespace : namespace } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
Type : v1 . ServiceTypeExternalName ,
} ,
} ,
} ,
{
desc : "External name with ports should not receive endpoints" ,
service : & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : serviceName , Namespace : namespace } ,
Spec : v1 . ServiceSpec {
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
Type : v1 . ServiceTypeExternalName ,
} ,
} ,
} ,
{
desc : "External name with selector should not receive endpoints" ,
service : & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : serviceName , Namespace : namespace } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
Type : v1 . ServiceTypeExternalName ,
} ,
} ,
} ,
{
desc : "External name without selector and ports should not receive endpoints" ,
service : & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : serviceName , Namespace : namespace } ,
Spec : v1 . ServiceSpec {
Type : v1 . ServiceTypeExternalName ,
} ,
} ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
testServer , endpointsHandler := makeTestServer ( t , namespace )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2023-01-18 06:12:34 -05:00
err := endpoints . serviceStore . Add ( tc . service )
if err != nil {
t . Fatalf ( "Error adding service to service store: %v" , err )
}
2023-12-13 03:11:08 -05:00
err = endpoints . syncService ( tCtx , namespace + "/" + serviceName )
2023-01-18 06:12:34 -05:00
if err != nil {
t . Fatalf ( "Error syncing service: %v" , err )
}
endpointsHandler . ValidateRequestCount ( t , 0 )
} )
}
}
2015-02-18 17:43:37 -05:00
func TestSyncEndpointsProtocolUDP ( t * testing . T ) {
2015-04-16 19:18:02 -04:00
ns := "other"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 , Protocol : "UDP" } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "UDP" } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-04-16 19:18:02 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2016-01-13 20:43:52 -05:00
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2016-01-13 20:43:52 -05:00
} ,
2016-11-18 15:50:17 -05:00
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "UDP" } } ,
2016-01-13 20:43:52 -05:00
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2014-11-18 12:49:00 -05:00
}
2018-06-11 07:25:18 -04:00
func TestSyncEndpointsProtocolSCTP ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2018-06-11 07:25:18 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 , Protocol : "SCTP" } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2018-06-11 07:25:18 -04:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "SCTP" } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2018-06-11 07:25:18 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2018-06-11 07:25:18 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2018-06-11 07:25:18 -04:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2018-06-11 07:25:18 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "SCTP" } } ,
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2018-06-11 07:25:18 -04:00
}
2025-06-08 19:51:45 -04:00
// Compare to ConformanceIt("should serve endpoints on same port and different protocols")
// in test/e2e/network/service.go
func TestSyncEndpointsProtocolMultiProtocolSamePort ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
} )
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort {
{ Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "TCP" } ,
{ Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "UDP" } ,
} ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
} ,
} )
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
endpointsHandler . ValidateRequestCount ( t , 1 )
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Labels : map [ string ] string {
LabelManagedBy : ControllerName ,
v1 . IsHeadlessService : "" ,
} ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort {
{ Port : 8080 , Protocol : "UDP" } ,
{ Port : 8080 , Protocol : "TCP" } ,
} ,
} } ,
} )
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
}
2014-11-18 12:49:00 -05:00
func TestSyncEndpointsItemsEmptySelectorSelectsAll ( t * testing . T ) {
2015-04-16 19:18:02 -04:00
ns := "other"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-04-16 19:18:02 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2014-11-18 12:49:00 -05:00
Name : "foo" ,
2015-04-16 19:18:02 -04:00
Namespace : ns ,
2014-11-18 12:49:00 -05:00
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2014-11-18 12:49:00 -05:00
} ,
2016-11-18 15:50:17 -05:00
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
2015-02-17 08:24:05 -05:00
} } ,
2014-11-18 12:49:00 -05:00
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2014-11-18 12:49:00 -05:00
}
2015-09-09 21:28:53 -04:00
func TestSyncEndpointsItemsEmptySelectorSelectsAllNotReady ( t * testing . T ) {
ns := "other"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 0 , 1 , 1 , ipv4only )
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-09-09 21:28:53 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2015-09-09 21:28:53 -04:00
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2015-09-09 21:28:53 -04:00
} ,
2016-11-18 15:50:17 -05:00
Subsets : [ ] v1 . EndpointSubset { {
NotReadyAddresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
2015-09-09 21:28:53 -04:00
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2015-09-09 21:28:53 -04:00
}
func TestSyncEndpointsItemsEmptySelectorSelectsAllMixed ( t * testing . T ) {
ns := "other"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 1 , ipv4only )
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-09-09 21:28:53 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2015-09-09 21:28:53 -04:00
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2015-09-09 21:28:53 -04:00
} ,
2016-11-18 15:50:17 -05:00
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
NotReadyAddresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.5" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod1" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
2015-09-09 21:28:53 -04:00
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2015-09-09 21:28:53 -04:00
}
2014-09-26 16:34:55 -04:00
func TestSyncEndpointsItemsPreexisting ( t * testing . T ) {
2015-04-16 19:18:02 -04:00
ns := "bar"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2025-03-04 11:06:10 -05:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2025-03-04 11:06:10 -05:00
} ,
2017-06-19 11:47:29 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-04-16 19:18:02 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2014-10-22 13:02:02 -04:00
Name : "foo" ,
2015-04-16 19:18:02 -04:00
Namespace : ns ,
2014-10-07 16:51:28 -04:00
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2014-09-26 16:34:55 -04:00
} ,
2016-11-18 15:50:17 -05:00
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
2015-02-17 08:24:05 -05:00
} } ,
2014-09-26 16:34:55 -04:00
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2014-09-26 16:34:55 -04:00
}
func TestSyncEndpointsItemsPreexistingIdentical ( t * testing . T ) {
2017-01-21 22:36:02 -05:00
ns := metav1 . NamespaceDefault
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
ResourceVersion : "1" ,
Name : "foo" ,
Namespace : ns ,
2025-03-04 11:06:10 -05:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2025-03-04 11:06:10 -05:00
} ,
2017-06-19 11:47:29 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , metav1 . NamespaceDefault , 1 , 1 , 0 , ipv4only )
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-21 22:36:02 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : metav1 . NamespaceDefault } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-04-16 19:18:02 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
endpointsHandler . ValidateRequestCount ( t , 0 )
2014-09-26 16:34:55 -04:00
}
2025-10-21 18:55:30 -04:00
func TestSyncEndpointsItemsPreexistingIdenticalUnsorted ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
pod0 := testPod ( ns , 0 , 1 , true , ipv4only )
pod1 := testPod ( ns , 1 , 1 , true , ipv4only )
subsets := [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress {
// known to not be packed correctly according to the current hash
{ IP : pod1 . Status . PodIPs [ 0 ] . IP , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : pod1 . Name , Namespace : ns } } ,
{ IP : pod0 . Status . PodIPs [ 0 ] . IP , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : pod0 . Name , Namespace : ns } } ,
} ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
} }
// Assert that endpoints are not repacked correctly according to the current hash.
// We want to prove that this does not cause a re-sync.
repacked := endptspkg . RepackSubsets ( subsets )
if reflect . DeepEqual ( subsets , repacked ) {
t . Errorf ( "subsets were already in sorted order" )
}
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
ResourceVersion : "1" ,
Name : "foo" ,
Namespace : ns ,
Labels : map [ string ] string {
LabelManagedBy : ControllerName ,
} ,
} ,
Subsets : subsets ,
} )
endpoints . podStore . Add ( pod0 )
endpoints . podStore . Add ( pod1 )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : metav1 . NamespaceDefault } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
} ,
} )
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
// syncing should've been a no-op
endpointsHandler . ValidateRequestCount ( t , 0 )
}
2014-09-26 16:34:55 -04:00
func TestSyncEndpointsItems ( t * testing . T ) {
2015-04-16 19:18:02 -04:00
ns := "other"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 3 , 2 , 0 , ipv4only )
addPods ( endpoints . podStore , "blah" , 5 , 2 , 0 , ipv4only ) // make sure these aren't found!
2019-08-19 16:45:22 -04:00
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2015-04-16 19:18:02 -04:00
Selector : map [ string ] string { "foo" : "bar" } ,
2016-11-18 15:50:17 -05:00
Ports : [ ] v1 . ServicePort {
2023-03-14 11:17:48 -04:00
{ Name : "port0" , Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } ,
{ Name : "port1" , Port : 88 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8088 ) } ,
2015-04-16 19:18:02 -04:00
} ,
2025-02-11 17:17:02 -05:00
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-04-16 19:18:02 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , "other/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
2016-11-18 15:50:17 -05:00
expectedSubsets := [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress {
{ IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } ,
{ IP : "1.2.3.5" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod1" , Namespace : ns } } ,
{ IP : "1.2.3.6" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod2" , Namespace : ns } } ,
2015-03-20 17:24:43 -04:00
} ,
2016-11-18 15:50:17 -05:00
Ports : [ ] v1 . EndpointPort {
2015-03-13 11:16:41 -04:00
{ Name : "port0" , Port : 8080 , Protocol : "TCP" } ,
{ Name : "port1" , Port : 8088 , Protocol : "TCP" } ,
2015-03-20 17:24:43 -04:00
} ,
} }
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2014-10-07 16:51:28 -04:00
ResourceVersion : "" ,
2017-06-19 11:47:29 -04:00
Name : "foo" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2014-09-26 16:34:55 -04:00
} ,
2015-03-20 17:24:43 -04:00
Subsets : endptspkg . SortSubsets ( expectedSubsets ) ,
2014-09-26 16:34:55 -04:00
} )
2017-06-19 11:47:29 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints" , "POST" , & data )
2014-06-06 19:40:48 -04:00
}
2015-04-16 19:18:02 -04:00
func TestSyncEndpointsItemsWithLabels ( t * testing . T ) {
ns := "other"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 3 , 2 , 0 , ipv4only )
2015-04-16 19:18:02 -04:00
serviceLabels := map [ string ] string { "foo" : "bar" }
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2015-04-16 19:18:02 -04:00
Name : "foo" ,
Namespace : ns ,
Labels : serviceLabels ,
} ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2015-04-16 19:18:02 -04:00
Selector : map [ string ] string { "foo" : "bar" } ,
2016-11-18 15:50:17 -05:00
Ports : [ ] v1 . ServicePort {
2023-03-14 11:17:48 -04:00
{ Name : "port0" , Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } ,
{ Name : "port1" , Port : 88 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8088 ) } ,
2015-04-06 16:14:54 -04:00
} ,
2025-02-11 17:17:02 -05:00
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-04-06 16:14:54 -04:00
} ,
2015-04-16 19:18:02 -04:00
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
2016-11-18 15:50:17 -05:00
expectedSubsets := [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress {
{ IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } ,
{ IP : "1.2.3.5" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod1" , Namespace : ns } } ,
{ IP : "1.2.3.6" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod2" , Namespace : ns } } ,
2015-04-06 16:14:54 -04:00
} ,
2016-11-18 15:50:17 -05:00
Ports : [ ] v1 . EndpointPort {
2015-04-06 16:14:54 -04:00
{ Name : "port0" , Port : 8080 , Protocol : "TCP" } ,
{ Name : "port1" , Port : 8088 , Protocol : "TCP" } ,
} ,
} }
2019-08-19 15:55:37 -04:00
serviceLabels [ v1 . IsHeadlessService ] = ""
2025-03-07 10:52:54 -05:00
serviceLabels [ LabelManagedBy ] = ControllerName
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2015-04-06 16:14:54 -04:00
ResourceVersion : "" ,
2017-06-19 11:47:29 -04:00
Name : "foo" ,
2015-04-16 19:18:02 -04:00
Labels : serviceLabels ,
2015-04-06 16:14:54 -04:00
} ,
Subsets : endptspkg . SortSubsets ( expectedSubsets ) ,
} )
2017-06-19 11:47:29 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints" , "POST" , & data )
2015-04-06 16:14:54 -04:00
}
func TestSyncEndpointsItemsPreexistingLabelsChange ( t * testing . T ) {
2015-04-16 19:18:02 -04:00
ns := "bar"
2017-06-19 11:47:29 -04:00
testServer , endpointsHandler := makeTestServer ( t , ns )
2016-04-21 07:50:55 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-19 11:47:29 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Labels : map [ string ] string {
"foo" : "bar" ,
} ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2015-04-16 19:18:02 -04:00
serviceLabels := map [ string ] string { "baz" : "blah" }
2017-02-07 20:25:52 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2015-04-16 19:18:02 -04:00
Name : "foo" ,
Namespace : ns ,
Labels : serviceLabels ,
} ,
2016-11-18 15:50:17 -05:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2015-04-16 19:18:02 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2017-06-19 11:47:29 -04:00
2019-08-19 15:55:37 -04:00
serviceLabels [ v1 . IsHeadlessService ] = ""
2025-03-07 10:52:54 -05:00
serviceLabels [ LabelManagedBy ] = ControllerName
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2015-04-06 16:14:54 -04:00
Name : "foo" ,
2015-04-16 19:18:02 -04:00
Namespace : ns ,
2015-04-06 16:14:54 -04:00
ResourceVersion : "1" ,
2015-04-16 19:18:02 -04:00
Labels : serviceLabels ,
2015-04-06 16:14:54 -04:00
} ,
2016-11-18 15:50:17 -05:00
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
2015-04-06 16:14:54 -04:00
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2015-04-06 16:14:54 -04:00
}
2017-06-19 11:47:29 -04:00
func TestWaitsForAllInformersToBeSynced2 ( t * testing . T ) {
var tests = [ ] struct {
podsSynced func ( ) bool
servicesSynced func ( ) bool
endpointsSynced func ( ) bool
shouldUpdateEndpoints bool
} {
{ neverReady , alwaysReady , alwaysReady , false } ,
{ alwaysReady , neverReady , alwaysReady , false } ,
{ alwaysReady , alwaysReady , neverReady , false } ,
{ alwaysReady , alwaysReady , alwaysReady , true } ,
}
for _ , test := range tests {
func ( ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2019-08-19 16:45:22 -04:00
2017-06-19 11:47:29 -04:00
service := & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "TCP" } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2017-06-19 11:47:29 -04:00
} ,
}
endpoints . serviceStore . Add ( service )
2019-10-22 07:57:28 -04:00
endpoints . onServiceUpdate ( service )
2017-06-19 11:47:29 -04:00
endpoints . podsSynced = test . podsSynced
endpoints . servicesSynced = test . servicesSynced
endpoints . endpointsSynced = test . endpointsSynced
endpoints . workerLoopPeriod = 10 * time . Millisecond
2023-12-13 03:11:08 -05:00
go endpoints . Run ( tCtx , 1 )
2017-06-19 11:47:29 -04:00
2019-08-16 15:15:53 -04:00
// cache.WaitForNamedCacheSync has a 100ms poll period, and the endpoints worker has a 10ms period.
2017-06-19 11:47:29 -04:00
// To ensure we get all updates, including unexpected ones, we need to wait at least as long as
// a single cache sync period and worker period, with some fudge room.
time . Sleep ( 150 * time . Millisecond )
if test . shouldUpdateEndpoints {
// Ensure the work queue has been processed by looping for up to a second to prevent flakes.
wait . PollImmediate ( 50 * time . Millisecond , 1 * time . Second , func ( ) ( bool , error ) {
return endpoints . queue . Len ( ) == 0 , nil
} )
endpointsHandler . ValidateRequestCount ( t , 1 )
} else {
endpointsHandler . ValidateRequestCount ( t , 0 )
}
} ( )
}
}
2017-06-09 11:22:37 -04:00
func TestSyncEndpointsHeadlessService ( t * testing . T ) {
ns := "headless"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-09 11:22:37 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 , Protocol : "TCP" } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2019-11-15 15:30:42 -05:00
service := & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns , Labels : map [ string ] string { "a" : "b" } } ,
2017-06-09 11:22:37 -04:00
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
ClusterIP : api . ClusterIPNone ,
Ports : [ ] v1 . ServicePort { } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2017-06-09 11:22:37 -04:00
} ,
2019-11-15 15:30:42 -05:00
}
originalService := service . DeepCopy ( )
endpoints . serviceStore . Add ( service )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-06-09 11:22:37 -04:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-11-15 15:30:42 -05:00
"a" : "b" ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2017-06-09 11:22:37 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
2018-04-12 18:42:26 -04:00
Ports : [ ] v1 . EndpointPort { } ,
2017-06-09 11:22:37 -04:00
} } ,
} )
2019-11-15 15:30:42 -05:00
if ! reflect . DeepEqual ( originalService , service ) {
2023-03-23 14:34:03 -04:00
t . Fatalf ( "syncing endpoints changed service: %s" , cmp . Diff ( service , originalService ) )
2019-11-15 15:30:42 -05:00
}
2017-06-09 11:22:37 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2017-06-09 11:22:37 -04:00
}
2017-06-14 03:54:33 -04:00
func TestSyncEndpointsItemsExcludeNotReadyPodsWithRestartPolicyNeverAndPhaseFailed ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-14 03:54:33 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2025-03-04 11:06:10 -05:00
"foo" : "bar" ,
2017-06-14 03:54:33 -04:00
} ,
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
addNotReadyPodsWithSpecifiedRestartPolicyAndPhase ( endpoints . podStore , ns , 1 , 1 , v1 . RestartPolicyNever , v1 . PodFailed )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2017-06-14 03:54:33 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-06-14 03:54:33 -04:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2017-06-14 03:54:33 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2017-06-14 03:54:33 -04:00
}
func TestSyncEndpointsItemsExcludeNotReadyPodsWithRestartPolicyNeverAndPhaseSucceeded ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-14 03:54:33 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Labels : map [ string ] string {
"foo" : "bar" ,
} ,
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
addNotReadyPodsWithSpecifiedRestartPolicyAndPhase ( endpoints . podStore , ns , 1 , 1 , v1 . RestartPolicyNever , v1 . PodSucceeded )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2017-06-14 03:54:33 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-06-14 03:54:33 -04:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2017-06-14 03:54:33 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2017-06-14 03:54:33 -04:00
}
func TestSyncEndpointsItemsExcludeNotReadyPodsWithRestartPolicyOnFailureAndPhaseSucceeded ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2017-06-14 03:54:33 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Labels : map [ string ] string {
"foo" : "bar" ,
} ,
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
addNotReadyPodsWithSpecifiedRestartPolicyAndPhase ( endpoints . podStore , ns , 1 , 1 , v1 . RestartPolicyOnFailure , v1 . PodSucceeded )
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2017-06-14 03:54:33 -04:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2017-06-14 03:54:33 -04:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2017-06-14 03:54:33 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2017-06-14 03:54:33 -04:00
}
2018-08-20 18:32:52 -04:00
func TestSyncEndpointsHeadlessWithoutPort ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2018-08-20 18:32:52 -04:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
ClusterIP : "None" ,
Ports : nil ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2018-08-20 18:32:52 -04:00
} ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2018-08-20 18:32:52 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2018-08-20 18:32:52 -04:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2018-08-20 18:32:52 -04:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : nil ,
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints" , "POST" , & data )
2018-08-20 18:32:52 -04:00
}
2019-08-19 16:45:22 -04:00
func TestPodToEndpointAddressForService ( t * testing . T ) {
2020-05-24 18:27:20 -04:00
ipv4 := v1 . IPv4Protocol
ipv6 := v1 . IPv6Protocol
2019-08-19 16:45:22 -04:00
testCases := [ ] struct {
2021-09-24 19:30:22 -04:00
name string
ipFamilies [ ] v1 . IPFamily
service v1 . Service
2020-05-24 18:27:20 -04:00
expectedEndpointFamily v1 . IPFamily
expectError bool
2019-08-19 16:45:22 -04:00
} {
{
2021-09-24 19:30:22 -04:00
name : "v4 service, in a single stack cluster" ,
ipFamilies : ipv4only ,
2019-08-19 16:45:22 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
ClusterIP : "10.0.0.1" ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2019-08-19 16:45:22 -04:00
} ,
} ,
2020-05-24 18:27:20 -04:00
expectedEndpointFamily : ipv4 ,
2019-08-19 16:45:22 -04:00
} ,
{
2021-09-24 19:30:22 -04:00
name : "v4 service, in a dual stack cluster" ,
ipFamilies : ipv4ipv6 ,
2019-08-19 16:45:22 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
ClusterIP : "10.0.0.1" ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2019-08-19 16:45:22 -04:00
} ,
} ,
2020-05-24 18:27:20 -04:00
expectedEndpointFamily : ipv4 ,
} ,
{
2021-09-24 19:30:22 -04:00
name : "v4 service, in a dual stack ipv6-primary cluster" ,
ipFamilies : ipv6ipv4 ,
2020-05-24 18:27:20 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
ClusterIP : "10.0.0.1" ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2020-05-24 18:27:20 -04:00
} ,
} ,
expectedEndpointFamily : ipv4 ,
} ,
{
2021-09-24 19:30:22 -04:00
name : "v4 headless service, in a single stack cluster" ,
ipFamilies : ipv4only ,
2020-05-24 18:27:20 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
ClusterIP : v1 . ClusterIPNone ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2020-05-24 18:27:20 -04:00
} ,
} ,
expectedEndpointFamily : ipv4 ,
} ,
{
2021-09-24 19:30:22 -04:00
name : "v4 headless service, in a dual stack cluster" ,
ipFamilies : ipv4ipv6 ,
2020-05-24 18:27:20 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
dual stack services (#91824)
* api: structure change
* api: defaulting, conversion, and validation
* [FIX] validation: auto remove second ip/family when service changes to SingleStack
* [FIX] api: defaulting, conversion, and validation
* api-server: clusterIPs alloc, printers, storage and strategy
* [FIX] clusterIPs default on read
* alloc: auto remove second ip/family when service changes to SingleStack
* api-server: repair loop handling for clusterIPs
* api-server: force kubernetes default service into single stack
* api-server: tie dualstack feature flag with endpoint feature flag
* controller-manager: feature flag, endpoint, and endpointSlice controllers handling multi family service
* [FIX] controller-manager: feature flag, endpoint, and endpointSlicecontrollers handling multi family service
* kube-proxy: feature-flag, utils, proxier, and meta proxier
* [FIX] kubeproxy: call both proxier at the same time
* kubenet: remove forced pod IP sorting
* kubectl: modify describe to include ClusterIPs, IPFamilies, and IPFamilyPolicy
* e2e: fix tests that depends on IPFamily field AND add dual stack tests
* e2e: fix expected error message for ClusterIP immutability
* add integration tests for dualstack
the third phase of dual stack is a very complex change in the API,
basically it introduces Dual Stack services. Main changes are:
- It pluralizes the Service IPFamily field to IPFamilies,
and removes the singular field.
- It introduces a new field IPFamilyPolicyType that can take
3 values to express the "dual-stack(mad)ness" of the cluster:
SingleStack, PreferDualStack and RequireDualStack
- It pluralizes ClusterIP to ClusterIPs.
The goal is to add coverage to the services API operations,
taking into account the 6 different modes a cluster can have:
- single stack: IP4 or IPv6 (as of today)
- dual stack: IPv4 only, IPv6 only, IPv4 - IPv6, IPv6 - IPv4
* [FIX] add integration tests for dualstack
* generated data
* generated files
Co-authored-by: Antonio Ojea <aojea@redhat.com>
2020-10-26 16:15:59 -04:00
ClusterIP : v1 . ClusterIPNone ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2020-05-24 18:27:20 -04:00
} ,
} ,
expectedEndpointFamily : ipv4 ,
} ,
{
2021-09-24 19:30:22 -04:00
name : "v6 service, in a dual stack cluster" ,
ipFamilies : ipv4ipv6 ,
2019-08-19 16:45:22 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
ClusterIP : "3000::1" ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv6Protocol } ,
2019-08-19 16:45:22 -04:00
} ,
} ,
2020-05-24 18:27:20 -04:00
expectedEndpointFamily : ipv6 ,
} ,
{
2021-09-24 19:30:22 -04:00
name : "v6 headless service, in a single stack cluster" ,
ipFamilies : ipv6only ,
2020-05-24 18:27:20 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
ClusterIP : v1 . ClusterIPNone ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv6Protocol } ,
2020-05-24 18:27:20 -04:00
} ,
} ,
expectedEndpointFamily : ipv6 ,
} ,
2020-05-24 17:48:59 -04:00
{
2025-02-11 17:17:02 -05:00
name : "v6 headless service, in a dual stack cluster" ,
2021-09-24 19:30:22 -04:00
ipFamilies : ipv4ipv6 ,
2020-05-24 17:48:59 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
dual stack services (#91824)
* api: structure change
* api: defaulting, conversion, and validation
* [FIX] validation: auto remove second ip/family when service changes to SingleStack
* [FIX] api: defaulting, conversion, and validation
* api-server: clusterIPs alloc, printers, storage and strategy
* [FIX] clusterIPs default on read
* alloc: auto remove second ip/family when service changes to SingleStack
* api-server: repair loop handling for clusterIPs
* api-server: force kubernetes default service into single stack
* api-server: tie dualstack feature flag with endpoint feature flag
* controller-manager: feature flag, endpoint, and endpointSlice controllers handling multi family service
* [FIX] controller-manager: feature flag, endpoint, and endpointSlicecontrollers handling multi family service
* kube-proxy: feature-flag, utils, proxier, and meta proxier
* [FIX] kubeproxy: call both proxier at the same time
* kubenet: remove forced pod IP sorting
* kubectl: modify describe to include ClusterIPs, IPFamilies, and IPFamilyPolicy
* e2e: fix tests that depends on IPFamily field AND add dual stack tests
* e2e: fix expected error message for ClusterIP immutability
* add integration tests for dualstack
the third phase of dual stack is a very complex change in the API,
basically it introduces Dual Stack services. Main changes are:
- It pluralizes the Service IPFamily field to IPFamilies,
and removes the singular field.
- It introduces a new field IPFamilyPolicyType that can take
3 values to express the "dual-stack(mad)ness" of the cluster:
SingleStack, PreferDualStack and RequireDualStack
- It pluralizes ClusterIP to ClusterIPs.
The goal is to add coverage to the services API operations,
taking into account the 6 different modes a cluster can have:
- single stack: IP4 or IPv6 (as of today)
- dual stack: IPv4 only, IPv6 only, IPv4 - IPv6, IPv6 - IPv4
* [FIX] add integration tests for dualstack
* generated data
* generated files
Co-authored-by: Antonio Ojea <aojea@redhat.com>
2020-10-26 16:15:59 -04:00
ClusterIP : v1 . ClusterIPNone ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv6Protocol } , // <- set by a api-server defaulting logic
2020-05-24 17:48:59 -04:00
} ,
} ,
expectedEndpointFamily : ipv6 ,
} ,
2019-08-19 16:45:22 -04:00
{
2021-09-24 19:30:22 -04:00
name : "v6 service, in a v4 only cluster" ,
ipFamilies : ipv4only ,
2019-08-19 16:45:22 -04:00
service : v1 . Service {
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
ClusterIP : "3000::1" ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv6Protocol } ,
2019-08-19 16:45:22 -04:00
} ,
} ,
2020-05-24 18:27:20 -04:00
expectError : true ,
2019-08-19 16:45:22 -04:00
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
podStore := cache . NewStore ( cache . DeletionHandlingMetaNamespaceKeyFunc )
ns := "test"
2025-02-11 17:43:17 -05:00
// We use addBadIPPod to test that podToEndpointAddressForService
// fixes up the bad IP.
addBadIPPod ( podStore , ns , tc . ipFamilies )
2019-08-19 16:45:22 -04:00
pods := podStore . List ( )
if len ( pods ) != 1 {
t . Fatalf ( "podStore size: expected: %d, got: %d" , 1 , len ( pods ) )
}
pod := pods [ 0 ] . ( * v1 . Pod )
epa , err := podToEndpointAddressForService ( & tc . service , pod )
if err != nil && ! tc . expectError {
t . Fatalf ( "podToEndpointAddressForService returned unexpected error %v" , err )
}
if err == nil && tc . expectError {
t . Fatalf ( "podToEndpointAddressForService should have returned error but it did not" )
}
if err != nil && tc . expectError {
return
}
2020-05-24 18:27:20 -04:00
if utilnet . IsIPv6String ( epa . IP ) != ( tc . expectedEndpointFamily == ipv6 ) {
t . Fatalf ( "IP: expected %s, got: %s" , tc . expectedEndpointFamily , epa . IP )
2019-08-19 16:45:22 -04:00
}
2025-02-11 17:43:17 -05:00
if strings . HasPrefix ( epa . IP , "0" ) {
t . Fatalf ( "IP: expected valid, got: %s" , epa . IP )
}
2019-08-19 16:45:22 -04:00
if * ( epa . NodeName ) != pod . Spec . NodeName {
t . Fatalf ( "NodeName: expected: %s, got: %s" , pod . Spec . NodeName , * ( epa . NodeName ) )
}
if epa . TargetRef . Kind != "Pod" {
t . Fatalf ( "TargetRef.Kind: expected: %s, got: %s" , "Pod" , epa . TargetRef . Kind )
}
if epa . TargetRef . Namespace != pod . ObjectMeta . Namespace {
2022-02-17 01:10:49 -05:00
t . Fatalf ( "TargetRef.Namespace: expected: %s, got: %s" , pod . ObjectMeta . Namespace , epa . TargetRef . Namespace )
2019-08-19 16:45:22 -04:00
}
if epa . TargetRef . Name != pod . ObjectMeta . Name {
2022-02-17 01:10:49 -05:00
t . Fatalf ( "TargetRef.Name: expected: %s, got: %s" , pod . ObjectMeta . Name , epa . TargetRef . Name )
2019-08-19 16:45:22 -04:00
}
if epa . TargetRef . UID != pod . ObjectMeta . UID {
2022-02-17 01:10:49 -05:00
t . Fatalf ( "TargetRef.UID: expected: %s, got: %s" , pod . ObjectMeta . UID , epa . TargetRef . UID )
2019-08-19 16:45:22 -04:00
}
2022-02-17 01:10:49 -05:00
if epa . TargetRef . ResourceVersion != "" {
t . Fatalf ( "TargetRef.ResourceVersion: expected empty, got: %s" , epa . TargetRef . ResourceVersion )
2019-08-19 16:45:22 -04:00
}
} )
}
}
2017-08-18 16:09:46 -04:00
2019-01-24 10:37:58 -05:00
func TestLastTriggerChangeTimeAnnotation ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2019-01-24 10:37:58 -05:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 , Protocol : "TCP" } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2019-01-24 10:37:58 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns , CreationTimestamp : metav1 . NewTime ( triggerTime ) } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "TCP" } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2019-01-24 10:37:58 -05:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2019-01-24 10:37:58 -05:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2019-01-24 10:37:58 -05:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Annotations : map [ string ] string {
v1 . EndpointsLastChangeTriggerTime : triggerTimeString ,
} ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2019-01-24 10:37:58 -05:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2019-01-24 10:37:58 -05:00
}
func TestLastTriggerChangeTimeAnnotation_AnnotationOverridden ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2019-01-24 10:37:58 -05:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Annotations : map [ string ] string {
v1 . EndpointsLastChangeTriggerTime : oldTriggerTimeString ,
} ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 , Protocol : "TCP" } } ,
} } ,
} )
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2019-01-24 10:37:58 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns , CreationTimestamp : metav1 . NewTime ( triggerTime ) } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "TCP" } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2019-01-24 10:37:58 -05:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2019-01-24 10:37:58 -05:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2019-01-24 10:37:58 -05:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Annotations : map [ string ] string {
v1 . EndpointsLastChangeTriggerTime : triggerTimeString ,
} ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} ,
2019-01-24 10:37:58 -05:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2019-01-24 10:37:58 -05:00
}
2019-02-20 05:48:24 -05:00
func TestLastTriggerChangeTimeAnnotation_AnnotationCleared ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 * time . Second )
2019-02-20 05:48:24 -05:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
Annotations : map [ string ] string {
v1 . EndpointsLastChangeTriggerTime : triggerTimeString ,
} ,
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "6.7.8.9" , NodeName : & emptyNodeName } } ,
Ports : [ ] v1 . EndpointPort { { Port : 1000 , Protocol : "TCP" } } ,
} } ,
} )
// Neither pod nor service has trigger time, this should cause annotation to be cleared.
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , 1 , 1 , 0 , ipv4only )
2019-02-20 05:48:24 -05:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { } ,
Ports : [ ] v1 . ServicePort { { Port : 80 , TargetPort : intstr . FromInt32 ( 8080 ) , Protocol : "TCP" } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2019-02-20 05:48:24 -05:00
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2019-02-20 05:48:24 -05:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
data := runtime . EncodeOrDie ( clientscheme . Codecs . LegacyCodec ( v1 . SchemeGroupVersion ) , & v1 . Endpoints {
2019-02-20 05:48:24 -05:00
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
2019-08-19 15:55:37 -04:00
Labels : map [ string ] string {
2025-03-07 10:52:54 -05:00
LabelManagedBy : ControllerName ,
2019-08-19 15:55:37 -04:00
v1 . IsHeadlessService : "" ,
} , // Annotation not set anymore.
2019-02-20 05:48:24 -05:00
} ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress { { IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } } ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : "TCP" } } ,
} } ,
} )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "PUT" , & data )
2019-02-20 05:48:24 -05:00
}
2019-07-24 05:01:42 -04:00
// TestPodUpdatesBatching verifies that endpoint updates caused by pod updates are batched together.
// This test uses real time.Sleep, as there is no easy way to mock time in endpoints controller now.
// TODO(mborsz): Migrate this test to mock clock when possible.
func TestPodUpdatesBatching ( t * testing . T ) {
type podUpdate struct {
delay time . Duration
podName string
podIP string
}
tests := [ ] struct {
name string
batchPeriod time . Duration
podsCount int
updates [ ] podUpdate
finalDelay time . Duration
wantRequestCount int
} {
{
name : "three updates with no batching" ,
batchPeriod : 0 * time . Second ,
podsCount : 10 ,
updates : [ ] podUpdate {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
podName : "pod0" ,
podIP : "10.0.0.0" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod1" ,
podIP : "10.0.0.1" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod2" ,
podIP : "10.0.0.2" ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 3 ,
} ,
{
name : "three updates in one batch" ,
batchPeriod : 1 * time . Second ,
podsCount : 10 ,
updates : [ ] podUpdate {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
podName : "pod0" ,
podIP : "10.0.0.0" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod1" ,
podIP : "10.0.0.1" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod2" ,
podIP : "10.0.0.2" ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 1 ,
} ,
{
name : "three updates in two batches" ,
batchPeriod : 1 * time . Second ,
podsCount : 10 ,
updates : [ ] podUpdate {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
podName : "pod0" ,
podIP : "10.0.0.0" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod1" ,
podIP : "10.0.0.1" ,
} ,
{
delay : 1 * time . Second ,
podName : "pod2" ,
podIP : "10.0.0.2" ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 2 ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
ns := "other"
resourceVersion := 1
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , tc . batchPeriod )
2019-07-24 05:01:42 -04:00
endpoints . podsSynced = alwaysReady
endpoints . servicesSynced = alwaysReady
endpoints . endpointsSynced = alwaysReady
endpoints . workerLoopPeriod = 10 * time . Millisecond
2023-12-13 03:11:08 -05:00
go endpoints . Run ( tCtx , 1 )
2019-07-24 05:01:42 -04:00
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , tc . podsCount , 1 , 0 , ipv4only )
2019-07-24 05:01:42 -04:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2019-07-24 05:01:42 -04:00
} ,
} )
for _ , update := range tc . updates {
time . Sleep ( update . delay )
old , exists , err := endpoints . podStore . GetByKey ( fmt . Sprintf ( "%s/%s" , ns , update . podName ) )
if err != nil {
t . Fatalf ( "Error while retrieving old value of %q: %v" , update . podName , err )
}
if ! exists {
t . Fatalf ( "Pod %q doesn't exist" , update . podName )
}
oldPod := old . ( * v1 . Pod )
newPod := oldPod . DeepCopy ( )
newPod . Status . PodIP = update . podIP
2020-05-24 11:55:09 -04:00
newPod . Status . PodIPs [ 0 ] . IP = update . podIP
2019-07-24 05:01:42 -04:00
newPod . ResourceVersion = strconv . Itoa ( resourceVersion )
resourceVersion ++
endpoints . podStore . Update ( newPod )
2025-10-17 18:58:24 -04:00
endpoints . onPodUpdate ( oldPod , newPod )
2019-07-24 05:01:42 -04:00
}
time . Sleep ( tc . finalDelay )
endpointsHandler . ValidateRequestCount ( t , tc . wantRequestCount )
} )
}
}
// TestPodAddsBatching verifies that endpoint updates caused by pod addition are batched together.
// This test uses real time.Sleep, as there is no easy way to mock time in endpoints controller now.
// TODO(mborsz): Migrate this test to mock clock when possible.
func TestPodAddsBatching ( t * testing . T ) {
type podAdd struct {
delay time . Duration
}
tests := [ ] struct {
name string
batchPeriod time . Duration
adds [ ] podAdd
finalDelay time . Duration
wantRequestCount int
} {
{
name : "three adds with no batching" ,
batchPeriod : 0 * time . Second ,
adds : [ ] podAdd {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
} ,
{
delay : 100 * time . Millisecond ,
} ,
{
delay : 100 * time . Millisecond ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 3 ,
} ,
{
name : "three adds in one batch" ,
batchPeriod : 1 * time . Second ,
adds : [ ] podAdd {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
} ,
{
delay : 100 * time . Millisecond ,
} ,
{
delay : 100 * time . Millisecond ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 1 ,
} ,
{
name : "three adds in two batches" ,
batchPeriod : 1 * time . Second ,
adds : [ ] podAdd {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
} ,
{
delay : 100 * time . Millisecond ,
} ,
{
delay : 1 * time . Second ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 2 ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , tc . batchPeriod )
2019-07-24 05:01:42 -04:00
endpoints . podsSynced = alwaysReady
endpoints . servicesSynced = alwaysReady
endpoints . endpointsSynced = alwaysReady
endpoints . workerLoopPeriod = 10 * time . Millisecond
2023-12-13 03:11:08 -05:00
go endpoints . Run ( tCtx , 1 )
2019-07-24 05:01:42 -04:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2019-07-24 05:01:42 -04:00
} ,
} )
for i , add := range tc . adds {
time . Sleep ( add . delay )
2020-05-24 18:27:20 -04:00
p := testPod ( ns , i , 1 , true , ipv4only )
2019-07-24 05:01:42 -04:00
endpoints . podStore . Add ( p )
2025-10-17 18:58:24 -04:00
endpoints . onPodUpdate ( nil , p )
2019-07-24 05:01:42 -04:00
}
time . Sleep ( tc . finalDelay )
endpointsHandler . ValidateRequestCount ( t , tc . wantRequestCount )
} )
}
}
// TestPodDeleteBatching verifies that endpoint updates caused by pod deletion are batched together.
// This test uses real time.Sleep, as there is no easy way to mock time in endpoints controller now.
// TODO(mborsz): Migrate this test to mock clock when possible.
func TestPodDeleteBatching ( t * testing . T ) {
type podDelete struct {
delay time . Duration
podName string
}
tests := [ ] struct {
name string
batchPeriod time . Duration
podsCount int
deletes [ ] podDelete
finalDelay time . Duration
wantRequestCount int
} {
{
name : "three deletes with no batching" ,
batchPeriod : 0 * time . Second ,
podsCount : 10 ,
deletes : [ ] podDelete {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
podName : "pod0" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod1" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod2" ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 3 ,
} ,
{
name : "three deletes in one batch" ,
batchPeriod : 1 * time . Second ,
podsCount : 10 ,
deletes : [ ] podDelete {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
podName : "pod0" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod1" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod2" ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 1 ,
} ,
{
name : "three deletes in two batches" ,
batchPeriod : 1 * time . Second ,
podsCount : 10 ,
deletes : [ ] podDelete {
{
// endpoints.Run needs ~100 ms to start processing updates.
delay : 200 * time . Millisecond ,
podName : "pod0" ,
} ,
{
delay : 100 * time . Millisecond ,
podName : "pod1" ,
} ,
{
delay : 1 * time . Second ,
podName : "pod2" ,
} ,
} ,
finalDelay : 3 * time . Second ,
wantRequestCount : 2 ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
ns := "other"
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , tc . batchPeriod )
2019-07-24 05:01:42 -04:00
endpoints . podsSynced = alwaysReady
endpoints . servicesSynced = alwaysReady
endpoints . endpointsSynced = alwaysReady
endpoints . workerLoopPeriod = 10 * time . Millisecond
2023-12-13 03:11:08 -05:00
go endpoints . Run ( tCtx , 1 )
2019-07-24 05:01:42 -04:00
2020-05-24 18:27:20 -04:00
addPods ( endpoints . podStore , ns , tc . podsCount , 1 , 0 , ipv4only )
2019-07-24 05:01:42 -04:00
endpoints . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2019-07-24 05:01:42 -04:00
} ,
} )
for _ , update := range tc . deletes {
time . Sleep ( update . delay )
old , exists , err := endpoints . podStore . GetByKey ( fmt . Sprintf ( "%s/%s" , ns , update . podName ) )
if err != nil {
t . Fatalf ( "Error while retrieving old value of %q: %v" , update . podName , err )
}
if ! exists {
t . Fatalf ( "Pod %q doesn't exist" , update . podName )
}
endpoints . podStore . Delete ( old )
2025-10-17 18:58:24 -04:00
endpoints . onPodUpdate ( old , nil )
2019-07-24 05:01:42 -04:00
}
time . Sleep ( tc . finalDelay )
endpointsHandler . ValidateRequestCount ( t , tc . wantRequestCount )
} )
}
}
2019-07-31 18:09:35 -04:00
func TestSyncEndpointsServiceNotFound ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , endpointsHandler := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
endpoints := newController ( tCtx , testServer . URL , 0 )
2019-07-31 18:09:35 -04:00
endpoints . endpointsStore . Add ( & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "1" ,
} ,
} )
2023-12-13 03:11:08 -05:00
err := endpoints . syncService ( tCtx , ns + "/foo" )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2019-07-31 18:09:35 -04:00
endpointsHandler . ValidateRequestCount ( t , 1 )
2019-12-13 11:28:11 -05:00
endpointsHandler . ValidateRequest ( t , "/api/v1/namespaces/" + ns + "/endpoints/foo" , "DELETE" , nil )
2019-07-31 18:09:35 -04:00
}
2019-07-30 18:42:01 -04:00
2021-03-08 20:54:18 -05:00
func TestSyncServiceOverCapacity ( t * testing . T ) {
testCases := [ ] struct {
2021-07-06 14:26:46 -04:00
name string
startingAnnotation * string
numExisting int
numDesired int
numDesiredNotReady int
numExpectedReady int
numExpectedNotReady int
expectedAnnotation bool
2021-03-08 20:54:18 -05:00
} { {
2021-07-06 14:26:46 -04:00
name : "empty" ,
startingAnnotation : nil ,
numExisting : 0 ,
numDesired : 0 ,
numExpectedReady : 0 ,
numExpectedNotReady : 0 ,
expectedAnnotation : false ,
2021-03-08 20:54:18 -05:00
} , {
2021-07-06 14:26:46 -04:00
name : "annotation added past capacity, < than maxCapacity of Ready Addresses" ,
startingAnnotation : nil ,
numExisting : maxCapacity - 1 ,
numDesired : maxCapacity - 3 ,
numDesiredNotReady : 4 ,
numExpectedReady : maxCapacity - 3 ,
numExpectedNotReady : 3 ,
expectedAnnotation : true ,
2021-03-08 20:54:18 -05:00
} , {
2021-07-06 14:26:46 -04:00
name : "annotation added past capacity, maxCapacity of Ready Addresses " ,
startingAnnotation : nil ,
numExisting : maxCapacity - 1 ,
numDesired : maxCapacity ,
numDesiredNotReady : 10 ,
numExpectedReady : maxCapacity ,
numExpectedNotReady : 0 ,
expectedAnnotation : true ,
2021-03-08 20:54:18 -05:00
} , {
2021-07-06 14:26:46 -04:00
name : "annotation removed below capacity" ,
2025-07-07 07:22:36 -04:00
startingAnnotation : ptr . To ( "truncated" ) ,
2021-07-06 14:26:46 -04:00
numExisting : maxCapacity - 1 ,
numDesired : maxCapacity - 1 ,
numDesiredNotReady : 0 ,
numExpectedReady : maxCapacity - 1 ,
numExpectedNotReady : 0 ,
expectedAnnotation : false ,
2021-03-08 20:54:18 -05:00
} , {
2021-07-06 14:26:46 -04:00
name : "annotation was set to warning previously, annotation removed at capacity" ,
2025-07-07 07:22:36 -04:00
startingAnnotation : ptr . To ( "warning" ) ,
2021-07-06 14:26:46 -04:00
numExisting : maxCapacity ,
numDesired : maxCapacity ,
numDesiredNotReady : 0 ,
numExpectedReady : maxCapacity ,
numExpectedNotReady : 0 ,
expectedAnnotation : false ,
} , {
name : "annotation was set to warning previously but still over capacity" ,
2025-07-07 07:22:36 -04:00
startingAnnotation : ptr . To ( "warning" ) ,
2021-07-06 14:26:46 -04:00
numExisting : maxCapacity + 1 ,
numDesired : maxCapacity + 1 ,
numDesiredNotReady : 0 ,
numExpectedReady : maxCapacity ,
numExpectedNotReady : 0 ,
expectedAnnotation : true ,
} , {
name : "annotation removed at capacity" ,
2025-07-07 07:22:36 -04:00
startingAnnotation : ptr . To ( "truncated" ) ,
2021-07-06 14:26:46 -04:00
numExisting : maxCapacity ,
numDesired : maxCapacity ,
numDesiredNotReady : 0 ,
numExpectedReady : maxCapacity ,
numExpectedNotReady : 0 ,
expectedAnnotation : false ,
} , {
name : "no endpoints change, annotation value corrected" ,
2025-07-07 07:22:36 -04:00
startingAnnotation : ptr . To ( "invalid" ) ,
2021-07-06 14:26:46 -04:00
numExisting : maxCapacity + 1 ,
numDesired : maxCapacity + 1 ,
numDesiredNotReady : 0 ,
numExpectedReady : maxCapacity ,
numExpectedNotReady : 0 ,
expectedAnnotation : true ,
2021-03-08 20:54:18 -05:00
} }
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
2021-03-08 20:54:18 -05:00
ns := "test"
2023-12-13 03:11:08 -05:00
client , c := newFakeController ( tCtx , 0 * time . Second )
2021-03-08 20:54:18 -05:00
2021-07-06 14:26:46 -04:00
addPods ( c . podStore , ns , tc . numDesired , 1 , tc . numDesiredNotReady , ipv4only )
2021-03-08 20:54:18 -05:00
pods := c . podStore . List ( )
svc := & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2021-03-08 20:54:18 -05:00
} ,
}
c . serviceStore . Add ( svc )
subset := v1 . EndpointSubset { }
for i := 0 ; i < tc . numExisting ; i ++ {
pod := pods [ i ] . ( * v1 . Pod )
epa , _ := podToEndpointAddressForService ( svc , pod )
subset . Addresses = append ( subset . Addresses , * epa )
}
endpoints := & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : svc . Name ,
Namespace : ns ,
ResourceVersion : "1" ,
Annotations : map [ string ] string { } ,
} ,
Subsets : [ ] v1 . EndpointSubset { subset } ,
}
if tc . startingAnnotation != nil {
endpoints . Annotations [ v1 . EndpointsOverCapacity ] = * tc . startingAnnotation
}
c . endpointsStore . Add ( endpoints )
2023-12-13 03:11:08 -05:00
_ , err := client . CoreV1 ( ) . Endpoints ( ns ) . Create ( tCtx , endpoints , metav1 . CreateOptions { } )
if err != nil {
t . Fatalf ( "unexpected error creating endpoints: %v" , err )
}
2021-03-08 20:54:18 -05:00
2023-12-13 03:11:08 -05:00
err = c . syncService ( tCtx , fmt . Sprintf ( "%s/%s" , ns , svc . Name ) )
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2021-03-08 20:54:18 -05:00
2023-12-13 03:11:08 -05:00
actualEndpoints , err := client . CoreV1 ( ) . Endpoints ( ns ) . Get ( tCtx , endpoints . Name , metav1 . GetOptions { } )
2021-03-08 20:54:18 -05:00
if err != nil {
t . Fatalf ( "unexpected error getting endpoints: %v" , err )
}
actualAnnotation , ok := actualEndpoints . Annotations [ v1 . EndpointsOverCapacity ]
if tc . expectedAnnotation {
if ! ok {
t . Errorf ( "Expected EndpointsOverCapacity annotation to be set" )
2021-07-06 14:26:46 -04:00
} else if actualAnnotation != "truncated" {
t . Errorf ( "Expected EndpointsOverCapacity annotation to be 'truncated', got %s" , actualAnnotation )
2021-03-08 20:54:18 -05:00
}
} else {
if ok {
t . Errorf ( "Expected EndpointsOverCapacity annotation not to be set, got %s" , actualAnnotation )
}
}
2021-07-06 14:26:46 -04:00
numActualReady := 0
numActualNotReady := 0
for _ , subset := range actualEndpoints . Subsets {
numActualReady += len ( subset . Addresses )
numActualNotReady += len ( subset . NotReadyAddresses )
}
if numActualReady != tc . numExpectedReady {
t . Errorf ( "Unexpected number of actual ready Endpoints: got %d endpoints, want %d endpoints" , numActualReady , tc . numExpectedReady )
}
if numActualNotReady != tc . numExpectedNotReady {
t . Errorf ( "Unexpected number of actual not ready Endpoints: got %d endpoints, want %d endpoints" , numActualNotReady , tc . numExpectedNotReady )
}
} )
}
}
func TestTruncateEndpoints ( t * testing . T ) {
testCases := [ ] struct {
desc string
// subsetsReady, subsetsNotReady, expectedReady, expectedNotReady
// must all be the same length
subsetsReady [ ] int
subsetsNotReady [ ] int
expectedReady [ ] int
expectedNotReady [ ] int
} { {
desc : "empty" ,
subsetsReady : [ ] int { } ,
subsetsNotReady : [ ] int { } ,
expectedReady : [ ] int { } ,
expectedNotReady : [ ] int { } ,
} , {
desc : "total endpoints < max capacity" ,
subsetsReady : [ ] int { 50 , 100 , 100 , 100 , 100 } ,
subsetsNotReady : [ ] int { 50 , 100 , 100 , 100 , 100 } ,
expectedReady : [ ] int { 50 , 100 , 100 , 100 , 100 } ,
expectedNotReady : [ ] int { 50 , 100 , 100 , 100 , 100 } ,
} , {
desc : "total endpoints = max capacity" ,
subsetsReady : [ ] int { 100 , 100 , 100 , 100 , 100 } ,
subsetsNotReady : [ ] int { 100 , 100 , 100 , 100 , 100 } ,
expectedReady : [ ] int { 100 , 100 , 100 , 100 , 100 } ,
expectedNotReady : [ ] int { 100 , 100 , 100 , 100 , 100 } ,
} , {
desc : "total ready endpoints < max capacity, but total endpoints > max capacity" ,
subsetsReady : [ ] int { 90 , 110 , 50 , 10 , 20 } ,
subsetsNotReady : [ ] int { 101 , 200 , 200 , 201 , 298 } ,
expectedReady : [ ] int { 90 , 110 , 50 , 10 , 20 } ,
expectedNotReady : [ ] int { 73 , 144 , 144 , 145 , 214 } ,
} , {
desc : "total ready endpoints > max capacity" ,
subsetsReady : [ ] int { 205 , 400 , 402 , 400 , 693 } ,
subsetsNotReady : [ ] int { 100 , 200 , 200 , 200 , 300 } ,
expectedReady : [ ] int { 98 , 191 , 192 , 191 , 328 } ,
expectedNotReady : [ ] int { 0 , 0 , 0 , 0 , 0 } ,
} }
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
var subsets [ ] v1 . EndpointSubset
for subsetIndex , numReady := range tc . subsetsReady {
subset := v1 . EndpointSubset { }
for i := 0 ; i < numReady ; i ++ {
subset . Addresses = append ( subset . Addresses , v1 . EndpointAddress { } )
}
numNotReady := tc . subsetsNotReady [ subsetIndex ]
for i := 0 ; i < numNotReady ; i ++ {
subset . NotReadyAddresses = append ( subset . NotReadyAddresses , v1 . EndpointAddress { } )
}
subsets = append ( subsets , subset )
}
endpoints := & v1 . Endpoints { Subsets : subsets }
truncateEndpoints ( endpoints )
for i , subset := range endpoints . Subsets {
if len ( subset . Addresses ) != tc . expectedReady [ i ] {
t . Errorf ( "Unexpected number of actual ready Endpoints for subset %d: got %d endpoints, want %d endpoints" , i , len ( subset . Addresses ) , tc . expectedReady [ i ] )
}
if len ( subset . NotReadyAddresses ) != tc . expectedNotReady [ i ] {
t . Errorf ( "Unexpected number of actual not ready Endpoints for subset %d: got %d endpoints, want %d endpoints" , i , len ( subset . NotReadyAddresses ) , tc . expectedNotReady [ i ] )
}
}
2021-03-08 20:54:18 -05:00
} )
}
}
2020-02-18 20:30:57 -05:00
func TestEndpointPortFromServicePort ( t * testing . T ) {
2025-07-07 07:22:36 -04:00
http := ptr . To ( "http" )
2020-02-18 20:30:57 -05:00
testCases := map [ string ] struct {
serviceAppProtocol * string
expectedEndpointsAppProtocol * string
} {
2020-11-06 20:46:32 -05:00
"empty app protocol" : {
2020-02-18 20:30:57 -05:00
serviceAppProtocol : nil ,
expectedEndpointsAppProtocol : nil ,
} ,
2020-11-06 20:46:32 -05:00
"http app protocol" : {
2020-02-18 20:30:57 -05:00
serviceAppProtocol : http ,
expectedEndpointsAppProtocol : http ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
epp := endpointPortFromServicePort ( & v1 . ServicePort { Name : "test" , AppProtocol : tc . serviceAppProtocol } , 80 )
if epp . AppProtocol != tc . expectedEndpointsAppProtocol {
t . Errorf ( "Expected Endpoints AppProtocol to be %s, got %s" , stringVal ( tc . expectedEndpointsAppProtocol ) , stringVal ( epp . AppProtocol ) )
}
} )
}
}
2020-07-20 20:22:54 -04:00
// TestMultipleServiceChanges tests that endpoints that are not created because of an out of sync endpoints cache are eventually recreated
// A service will be created. After the endpoints exist, the service will be deleted and the endpoints will not be deleted from the cache immediately.
// After the service is recreated, the endpoints will be deleted replicating an out of sync cache. Expect that eventually the endpoints will be recreated.
func TestMultipleServiceChanges ( t * testing . T ) {
ns := metav1 . NamespaceDefault
expectedSubsets := [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress {
{ IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } ,
} ,
} }
endpoint := & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns , ResourceVersion : "1" } ,
Subsets : expectedSubsets ,
}
controller := & endpointController { }
blockDelete := make ( chan struct { } )
blockNextAction := make ( chan struct { } )
stopChan := make ( chan struct { } )
2024-06-24 13:57:06 -04:00
testServer := makeBlockingEndpointTestServer ( t , controller , endpoint , nil , blockDelete , blockNextAction , ns )
2020-07-20 20:22:54 -04:00
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
* controller = * newController ( tCtx , testServer . URL , 0 * time . Second )
2020-07-20 20:22:54 -04:00
addPods ( controller . podStore , ns , 1 , 1 , 0 , ipv4only )
2023-12-13 03:11:08 -05:00
go func ( ) { controller . Run ( tCtx , 1 ) } ( )
2020-07-20 20:22:54 -04:00
svc := & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
ClusterIP : "None" ,
Ports : nil ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2020-07-20 20:22:54 -04:00
} ,
}
controller . serviceStore . Add ( svc )
controller . onServiceUpdate ( svc )
// blockNextAction should eventually unblock once server gets endpoint request.
waitForChanReceive ( t , 1 * time . Second , blockNextAction , "Service Add should have caused a request to be sent to the test server" )
controller . serviceStore . Delete ( svc )
controller . onServiceDelete ( svc )
waitForChanReceive ( t , 1 * time . Second , blockNextAction , "Service Delete should have caused a request to be sent to the test server" )
// If endpoints cache has not updated before service update is registered
// Services add will not trigger a Create endpoint request.
controller . serviceStore . Add ( svc )
controller . onServiceUpdate ( svc )
// Ensure the work queue has been processed by looping for up to a second to prevent flakes.
wait . PollImmediate ( 50 * time . Millisecond , 1 * time . Second , func ( ) ( bool , error ) {
return controller . queue . Len ( ) == 0 , nil
} )
// Cause test server to delete endpoints
close ( blockDelete )
waitForChanReceive ( t , 1 * time . Second , blockNextAction , "Endpoint should have been recreated" )
close ( blockNextAction )
close ( stopChan )
}
2024-06-24 13:57:06 -04:00
// TestMultiplePodChanges tests that endpoints that are not updated because of an out of sync endpoints cache are
// eventually resynced after multiple Pod changes.
func TestMultiplePodChanges ( t * testing . T ) {
ns := metav1 . NamespaceDefault
readyEndpoints := & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns , ResourceVersion : "1" } ,
Subsets : [ ] v1 . EndpointSubset { {
Addresses : [ ] v1 . EndpointAddress {
{ IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } ,
} ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : v1 . ProtocolTCP } } ,
} } ,
}
notReadyEndpoints := & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns , ResourceVersion : "2" } ,
Subsets : [ ] v1 . EndpointSubset { {
NotReadyAddresses : [ ] v1 . EndpointAddress {
{ IP : "1.2.3.4" , NodeName : & emptyNodeName , TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod0" , Namespace : ns } } ,
} ,
Ports : [ ] v1 . EndpointPort { { Port : 8080 , Protocol : v1 . ProtocolTCP } } ,
} } ,
}
controller := & endpointController { }
blockUpdate := make ( chan struct { } )
blockNextAction := make ( chan struct { } )
stopChan := make ( chan struct { } )
testServer := makeBlockingEndpointTestServer ( t , controller , notReadyEndpoints , blockUpdate , nil , blockNextAction , ns )
defer testServer . Close ( )
tCtx := ktesting . Init ( t )
* controller = * newController ( tCtx , testServer . URL , 0 * time . Second )
pod := testPod ( ns , 0 , 1 , true , ipv4only )
_ = controller . podStore . Add ( pod )
_ = controller . endpointsStore . Add ( readyEndpoints )
_ = controller . serviceStore . Add ( & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : ns } ,
Spec : v1 . ServiceSpec {
2025-02-11 17:17:02 -05:00
Selector : map [ string ] string { "foo" : "bar" } ,
ClusterIP : "10.0.0.1" ,
Ports : [ ] v1 . ServicePort { { Port : 80 , Protocol : "TCP" , TargetPort : intstr . FromInt32 ( 8080 ) } } ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2024-06-24 13:57:06 -04:00
} ,
} )
go func ( ) { controller . Run ( tCtx , 1 ) } ( )
// Rapidly update the Pod: Ready -> NotReady -> Ready.
pod2 := pod . DeepCopy ( )
pod2 . ResourceVersion = "2"
pod2 . Status . Conditions [ 0 ] . Status = v1 . ConditionFalse
_ = controller . podStore . Update ( pod2 )
2025-10-17 18:58:24 -04:00
controller . onPodUpdate ( pod , pod2 )
2024-06-24 13:57:06 -04:00
// blockNextAction should eventually unblock once server gets endpoints request.
waitForChanReceive ( t , 1 * time . Second , blockNextAction , "Pod Update should have caused a request to be sent to the test server" )
// The endpoints update hasn't been applied to the cache yet.
pod3 := pod . DeepCopy ( )
pod3 . ResourceVersion = "3"
pod3 . Status . Conditions [ 0 ] . Status = v1 . ConditionTrue
_ = controller . podStore . Update ( pod3 )
2025-10-17 18:58:24 -04:00
controller . onPodUpdate ( pod2 , pod3 )
2024-06-24 13:57:06 -04:00
// It shouldn't get endpoints request as the endpoints in the cache is out-of-date.
timer := time . NewTimer ( 100 * time . Millisecond )
select {
case <- timer . C :
case <- blockNextAction :
t . Errorf ( "Pod Update shouldn't have caused a request to be sent to the test server" )
}
// Applying the endpoints update to the cache should cause test server to update endpoints.
close ( blockUpdate )
waitForChanReceive ( t , 1 * time . Second , blockNextAction , "Endpoints should have been updated" )
close ( blockNextAction )
close ( stopChan )
}
2022-05-22 06:21:24 -04:00
func TestSyncServiceAddresses ( t * testing . T ) {
makeService := func ( tolerateUnready bool ) * v1 . Service {
return & v1 . Service {
ObjectMeta : metav1 . ObjectMeta { Name : "foo" , Namespace : "ns" } ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
PublishNotReadyAddresses : tolerateUnready ,
Type : v1 . ServiceTypeClusterIP ,
ClusterIP : "1.1.1.1" ,
Ports : [ ] v1 . ServicePort { { Port : 80 } } ,
2025-02-11 17:17:02 -05:00
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
2022-05-22 06:21:24 -04:00
} ,
}
}
makePod := func ( phase v1 . PodPhase , isReady bool , terminating bool ) * v1 . Pod {
statusCondition := v1 . ConditionFalse
if isReady {
statusCondition = v1 . ConditionTrue
}
now := metav1 . Now ( )
deletionTimestamp := & now
if ! terminating {
deletionTimestamp = nil
}
return & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Namespace : "ns" ,
Name : "fakepod" ,
DeletionTimestamp : deletionTimestamp ,
Labels : map [ string ] string { "foo" : "bar" } ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container { { Ports : [ ] v1 . ContainerPort {
{ Name : "port1" , ContainerPort : int32 ( 8080 ) } ,
} } } ,
} ,
Status : v1 . PodStatus {
Phase : phase ,
Conditions : [ ] v1 . PodCondition {
{
Type : v1 . PodReady ,
Status : statusCondition ,
} ,
} ,
PodIP : "10.1.1.1" ,
PodIPs : [ ] v1 . PodIP {
{ IP : "10.1.1.1" } ,
} ,
} ,
}
}
testCases := [ ] struct {
name string
pod * v1 . Pod
service * v1 . Service
expectedReady int
expectedUnready int
} {
{
name : "pod running phase" ,
pod : makePod ( v1 . PodRunning , true , false ) ,
service : makeService ( false ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod running phase being deleted" ,
pod : makePod ( v1 . PodRunning , true , true ) ,
service : makeService ( false ) ,
expectedReady : 0 ,
expectedUnready : 0 ,
} ,
{
name : "pod unknown phase container ready" ,
pod : makePod ( v1 . PodUnknown , true , false ) ,
service : makeService ( false ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod unknown phase container ready being deleted" ,
pod : makePod ( v1 . PodUnknown , true , true ) ,
service : makeService ( false ) ,
expectedReady : 0 ,
expectedUnready : 0 ,
} ,
{
name : "pod pending phase container ready" ,
pod : makePod ( v1 . PodPending , true , false ) ,
service : makeService ( false ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod pending phase container ready being deleted" ,
pod : makePod ( v1 . PodPending , true , true ) ,
service : makeService ( false ) ,
expectedReady : 0 ,
expectedUnready : 0 ,
} ,
{
name : "pod unknown phase container not ready" ,
pod : makePod ( v1 . PodUnknown , false , false ) ,
service : makeService ( false ) ,
expectedReady : 0 ,
expectedUnready : 1 ,
} ,
{
name : "pod pending phase container not ready" ,
pod : makePod ( v1 . PodPending , false , false ) ,
service : makeService ( false ) ,
expectedReady : 0 ,
expectedUnready : 1 ,
} ,
{
name : "pod failed phase" ,
pod : makePod ( v1 . PodFailed , false , false ) ,
service : makeService ( false ) ,
expectedReady : 0 ,
expectedUnready : 0 ,
} ,
{
name : "pod succeeded phase" ,
pod : makePod ( v1 . PodSucceeded , false , false ) ,
service : makeService ( false ) ,
expectedReady : 0 ,
expectedUnready : 0 ,
} ,
{
name : "pod running phase and tolerate unready" ,
pod : makePod ( v1 . PodRunning , false , false ) ,
service : makeService ( true ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod running phase and tolerate unready being deleted" ,
pod : makePod ( v1 . PodRunning , false , true ) ,
service : makeService ( true ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod unknown phase and tolerate unready" ,
pod : makePod ( v1 . PodUnknown , false , false ) ,
service : makeService ( true ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod unknown phase and tolerate unready being deleted" ,
pod : makePod ( v1 . PodUnknown , false , true ) ,
service : makeService ( true ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod pending phase and tolerate unready" ,
pod : makePod ( v1 . PodPending , false , false ) ,
service : makeService ( true ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod pending phase and tolerate unready being deleted" ,
pod : makePod ( v1 . PodPending , false , true ) ,
service : makeService ( true ) ,
expectedReady : 1 ,
expectedUnready : 0 ,
} ,
{
name : "pod failed phase and tolerate unready" ,
pod : makePod ( v1 . PodFailed , false , false ) ,
service : makeService ( true ) ,
expectedReady : 0 ,
expectedUnready : 0 ,
} ,
{
name : "pod succeeded phase and tolerate unready endpoints" ,
pod : makePod ( v1 . PodSucceeded , false , false ) ,
service : makeService ( true ) ,
expectedReady : 0 ,
expectedUnready : 0 ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
2022-05-22 06:21:24 -04:00
ns := tc . service . Namespace
2023-12-13 03:11:08 -05:00
client , c := newFakeController ( tCtx , 0 * time . Second )
2022-05-22 06:21:24 -04:00
err := c . podStore . Add ( tc . pod )
if err != nil {
t . Errorf ( "Unexpected error adding pod %v" , err )
}
err = c . serviceStore . Add ( tc . service )
if err != nil {
t . Errorf ( "Unexpected error adding service %v" , err )
}
2023-12-13 03:11:08 -05:00
err = c . syncService ( tCtx , fmt . Sprintf ( "%s/%s" , ns , tc . service . Name ) )
2022-05-22 06:21:24 -04:00
if err != nil {
t . Errorf ( "Unexpected error syncing service %v" , err )
}
2023-12-13 03:11:08 -05:00
endpoints , err := client . CoreV1 ( ) . Endpoints ( ns ) . Get ( tCtx , tc . service . Name , metav1 . GetOptions { } )
2022-05-22 06:21:24 -04:00
if err != nil {
t . Errorf ( "Unexpected error %v" , err )
}
readyEndpoints := 0
unreadyEndpoints := 0
for _ , subset := range endpoints . Subsets {
readyEndpoints += len ( subset . Addresses )
unreadyEndpoints += len ( subset . NotReadyAddresses )
}
if tc . expectedReady != readyEndpoints {
t . Errorf ( "Expected %d ready endpoints, got %d" , tc . expectedReady , readyEndpoints )
}
if tc . expectedUnready != unreadyEndpoints {
t . Errorf ( "Expected %d ready endpoints, got %d" , tc . expectedUnready , unreadyEndpoints )
}
} )
}
}
2020-07-20 20:22:54 -04:00
func TestEndpointsDeletionEvents ( t * testing . T ) {
ns := metav1 . NamespaceDefault
testServer , _ := makeTestServer ( t , ns )
defer testServer . Close ( )
2023-12-13 03:11:08 -05:00
tCtx := ktesting . Init ( t )
controller := newController ( tCtx , testServer . URL , 0 )
2020-07-20 20:22:54 -04:00
store := controller . endpointsStore
ep1 := & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : ns ,
ResourceVersion : "rv1" ,
} ,
}
// Test Unexpected and Expected Deletes
store . Delete ( ep1 )
controller . onEndpointsDelete ( ep1 )
if controller . queue . Len ( ) != 1 {
t . Errorf ( "Expected one service to be in the queue, found %d" , controller . queue . Len ( ) )
}
}
2020-02-18 20:30:57 -05:00
func stringVal ( str * string ) string {
if str == nil {
return "nil"
}
return * str
}
2020-07-20 20:22:54 -04:00
// waitForChanReceive blocks up to the timeout waiting for the receivingChan to receive
func waitForChanReceive ( t * testing . T , timeout time . Duration , receivingChan chan struct { } , errorMsg string ) {
timer := time . NewTimer ( timeout )
select {
case <- timer . C :
2024-09-12 08:45:22 -04:00
t . Error ( errorMsg )
2020-07-20 20:22:54 -04:00
case <- receivingChan :
}
}
2023-06-26 14:38:36 -04:00
func TestEndpointSubsetsEqualIgnoreResourceVersion ( t * testing . T ) {
copyAndMutateEndpointSubset := func ( orig * v1 . EndpointSubset , mutator func ( * v1 . EndpointSubset ) ) * v1 . EndpointSubset {
newSubSet := orig . DeepCopy ( )
mutator ( newSubSet )
return newSubSet
}
es1 := & v1 . EndpointSubset {
Addresses : [ ] v1 . EndpointAddress {
{
IP : "1.1.1.1" ,
TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod1-1" , Namespace : "ns" , ResourceVersion : "1" } ,
} ,
} ,
NotReadyAddresses : [ ] v1 . EndpointAddress {
{
IP : "1.1.1.2" ,
TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod1-2" , Namespace : "ns2" , ResourceVersion : "2" } ,
} ,
} ,
Ports : [ ] v1 . EndpointPort { { Port : 8081 , Protocol : "TCP" } } ,
}
es2 := & v1 . EndpointSubset {
Addresses : [ ] v1 . EndpointAddress {
{
IP : "2.2.2.1" ,
TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod2-1" , Namespace : "ns" , ResourceVersion : "3" } ,
} ,
} ,
NotReadyAddresses : [ ] v1 . EndpointAddress {
{
IP : "2.2.2.2" ,
TargetRef : & v1 . ObjectReference { Kind : "Pod" , Name : "pod2-2" , Namespace : "ns2" , ResourceVersion : "4" } ,
} ,
} ,
Ports : [ ] v1 . EndpointPort { { Port : 8082 , Protocol : "TCP" } } ,
}
tests := [ ] struct {
name string
subsets1 [ ] v1 . EndpointSubset
subsets2 [ ] v1 . EndpointSubset
expected bool
} {
{
name : "Subsets removed" ,
subsets1 : [ ] v1 . EndpointSubset { * es1 , * es2 } ,
subsets2 : [ ] v1 . EndpointSubset { * es1 } ,
expected : false ,
} ,
{
name : "Ready Pod IP changed" ,
subsets1 : [ ] v1 . EndpointSubset { * es1 , * es2 } ,
subsets2 : [ ] v1 . EndpointSubset { * copyAndMutateEndpointSubset ( es1 , func ( es * v1 . EndpointSubset ) {
es . Addresses [ 0 ] . IP = "1.1.1.10"
} ) , * es2 } ,
expected : false ,
} ,
{
name : "NotReady Pod IP changed" ,
subsets1 : [ ] v1 . EndpointSubset { * es1 , * es2 } ,
subsets2 : [ ] v1 . EndpointSubset { * es1 , * copyAndMutateEndpointSubset ( es2 , func ( es * v1 . EndpointSubset ) {
es . NotReadyAddresses [ 0 ] . IP = "2.2.2.10"
} ) } ,
expected : false ,
} ,
{
name : "Pod ResourceVersion changed" ,
subsets1 : [ ] v1 . EndpointSubset { * es1 , * es2 } ,
subsets2 : [ ] v1 . EndpointSubset { * es1 , * copyAndMutateEndpointSubset ( es2 , func ( es * v1 . EndpointSubset ) {
es . Addresses [ 0 ] . TargetRef . ResourceVersion = "100"
} ) } ,
expected : true ,
} ,
{
name : "Pod ResourceVersion removed" ,
subsets1 : [ ] v1 . EndpointSubset { * es1 , * es2 } ,
subsets2 : [ ] v1 . EndpointSubset { * es1 , * copyAndMutateEndpointSubset ( es2 , func ( es * v1 . EndpointSubset ) {
es . Addresses [ 0 ] . TargetRef . ResourceVersion = ""
} ) } ,
expected : true ,
} ,
{
name : "Ports changed" ,
subsets1 : [ ] v1 . EndpointSubset { * es1 , * es2 } ,
subsets2 : [ ] v1 . EndpointSubset { * es1 , * copyAndMutateEndpointSubset ( es1 , func ( es * v1 . EndpointSubset ) {
es . Ports [ 0 ] . Port = 8082
} ) } ,
expected : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if got := endpointSubsetsEqualIgnoreResourceVersion ( tt . subsets1 , tt . subsets2 ) ; got != tt . expected {
t . Errorf ( "semanticIgnoreResourceVersion.DeepEqual() = %v, expected %v" , got , tt . expected )
}
} )
}
}
2025-06-08 19:51:45 -04:00
// Compare to It("should be updated after adding or deleting ports") in
// test/e2e/network/service.go.
func TestSyncEndpointsAddDeletePorts ( t * testing . T ) {
ctx := ktesting . Init ( t )
client , c := newFakeController ( ctx , 0 * time . Second )
svc := & v1 . Service {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
Namespace : "ns" ,
} ,
Spec : v1 . ServiceSpec {
Selector : map [ string ] string { "foo" : "bar" } ,
Type : v1 . ServiceTypeClusterIP ,
ClusterIP : "1.1.1.1" ,
Ports : [ ] v1 . ServicePort {
{ Name : "portname1" , Port : 80 , TargetPort : intstr . FromString ( "svc1" ) } ,
} ,
IPFamilies : [ ] v1 . IPFamily { v1 . IPv4Protocol } ,
} ,
}
podTemplate := & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Namespace : svc . Namespace ,
Labels : map [ string ] string { "foo" : "bar" } ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container { { } } ,
} ,
Status : v1 . PodStatus {
Conditions : [ ] v1 . PodCondition {
{
Type : v1 . PodReady ,
Status : v1 . ConditionTrue ,
} ,
} ,
} ,
}
expectEndpoints := & v1 . Endpoints {
ObjectMeta : metav1 . ObjectMeta {
Name : svc . Name ,
Namespace : svc . Namespace ,
Annotations : map [ string ] string { } ,
Labels : map [ string ] string {
LabelManagedBy : ControllerName ,
} ,
ResourceVersion : "1" ,
} ,
Subsets : [ ] v1 . EndpointSubset { } ,
}
// Unit testing requires starting with a dummy Endpoints with a non-empty
// ResourceVersion.
err := c . endpointsStore . Add ( expectEndpoints )
if err != nil {
t . Fatalf ( "Unexpected error adding endpoints: %v" , err )
}
_ , err = client . CoreV1 ( ) . Endpoints ( svc . Namespace ) . Create ( ctx , expectEndpoints , metav1 . CreateOptions { } )
if err != nil {
t . Fatalf ( "Unexpected error adding endpoints: %v" , err )
}
err = c . serviceStore . Add ( svc )
if err != nil {
t . Fatalf ( "Unexpected error adding service: %v" , err )
}
pod1 := podTemplate . DeepCopy ( )
pod1 . Name = "pod1"
pod1 . Status . PodIP = "10.1.1.1"
pod1 . Status . PodIPs = [ ] v1 . PodIP { { IP : "10.1.1.1" } }
pod1 . Spec . Containers [ 0 ] . Ports = [ ] v1 . ContainerPort {
{ Name : "svc1" , ContainerPort : int32 ( 100 ) } ,
}
err = c . podStore . Add ( pod1 )
if err != nil {
t . Fatalf ( "Unexpected error adding pod: %v" , err )
}
err = c . syncService ( ctx , fmt . Sprintf ( "%s/%s" , svc . Namespace , svc . Name ) )
if err != nil {
t . Fatalf ( "Unexpected error syncing service: %v" , err )
}
endpoints , err := client . CoreV1 ( ) . Endpoints ( svc . Namespace ) . Get ( ctx , svc . Name , metav1 . GetOptions { } )
if err != nil {
t . Fatalf ( "Unexpected error: %v" , err )
}
expectEndpoints . Subsets = append ( expectEndpoints . Subsets ,
v1 . EndpointSubset {
Addresses : [ ] v1 . EndpointAddress { {
IP : pod1 . Status . PodIP ,
NodeName : & pod1 . Spec . NodeName ,
TargetRef : & v1 . ObjectReference {
Kind : "Pod" ,
Namespace : pod1 . Namespace ,
Name : pod1 . Name ,
} ,
} } ,
Ports : [ ] v1 . EndpointPort { {
Name : "portname1" ,
Port : 100 ,
} } ,
} ,
)
if diff := cmp . Diff ( expectEndpoints , endpoints ) ; diff != "" {
t . Fatalf ( "incorrect endpoints after adding first pod:\n%s" , diff )
}
// Add another port to the service
svc . Spec . Ports = append ( svc . Spec . Ports , v1 . ServicePort {
Name : "portname2" ,
Port : 81 ,
TargetPort : intstr . FromString ( "svc2" ) ,
} )
err = c . serviceStore . Add ( svc )
if err != nil {
t . Fatalf ( "Unexpected error updating service: %v" , err )
}
// Add another endpoint
pod2 := podTemplate . DeepCopy ( )
pod2 . Name = "pod2"
pod2 . Status . PodIP = "10.1.1.2"
pod2 . Status . PodIPs = [ ] v1 . PodIP { { IP : "10.1.1.2" } }
pod2 . Spec . Containers [ 0 ] . Ports = [ ] v1 . ContainerPort {
{ Name : "svc2" , ContainerPort : int32 ( 101 ) } ,
}
err = c . podStore . Add ( pod2 )
if err != nil {
t . Fatalf ( "Unexpected error adding pod: %v" , err )
}
err = c . syncService ( ctx , fmt . Sprintf ( "%s/%s" , svc . Namespace , svc . Name ) )
if err != nil {
t . Fatalf ( "Unexpected error syncing service: %v" , err )
}
endpoints , err = client . CoreV1 ( ) . Endpoints ( svc . Namespace ) . Get ( ctx , svc . Name , metav1 . GetOptions { } )
if err != nil {
t . Fatalf ( "Unexpected error: %v" , err )
}
expectEndpoints . Subsets = append ( expectEndpoints . Subsets ,
v1 . EndpointSubset {
Addresses : [ ] v1 . EndpointAddress { {
IP : pod2 . Status . PodIP ,
NodeName : & pod2 . Spec . NodeName ,
TargetRef : & v1 . ObjectReference {
Kind : "Pod" ,
Namespace : pod2 . Namespace ,
Name : pod2 . Name ,
} ,
} } ,
Ports : [ ] v1 . EndpointPort { {
Name : "portname2" ,
Port : 101 ,
} } ,
} ,
)
expectEndpoints . Subsets = endptspkg . RepackSubsets ( expectEndpoints . Subsets )
if diff := cmp . Diff ( expectEndpoints , endpoints ) ; diff != "" {
t . Fatalf ( "incorrect endpoints after adding second pod:\n%s" , diff )
}
// Delete a port from the service
svc . Spec . Ports = svc . Spec . Ports [ 1 : ]
err = c . serviceStore . Add ( svc )
if err != nil {
t . Fatalf ( "Unexpected error updating service: %v" , err )
}
err = c . syncService ( ctx , fmt . Sprintf ( "%s/%s" , svc . Namespace , svc . Name ) )
if err != nil {
t . Fatalf ( "Unexpected error syncing service: %v" , err )
}
endpoints , err = client . CoreV1 ( ) . Endpoints ( svc . Namespace ) . Get ( ctx , svc . Name , metav1 . GetOptions { } )
if err != nil {
t . Fatalf ( "Unexpected error: %v" , err )
}
expectEndpoints . Subsets = expectEndpoints . Subsets [ : 1 ]
if diff := cmp . Diff ( expectEndpoints , endpoints ) ; diff != "" {
t . Fatalf ( "incorrect endpoints after deleting first port:\n%s" , diff )
}
}