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>
This commit is contained in:

committed by
GitHub

parent
d0e06cf3e0
commit
6675eba3ef
@@ -85,10 +85,6 @@ func makeServicePortName(ns, name, port string, protocol v1.Protocol) ServicePor
|
||||
}
|
||||
|
||||
func TestServiceToServiceMap(t *testing.T) {
|
||||
svcTracker := NewServiceChangeTracker(nil, nil, nil, nil)
|
||||
|
||||
trueVal := true
|
||||
falseVal := false
|
||||
testClusterIPv4 := "10.0.0.1"
|
||||
testExternalIPv4 := "8.8.8.8"
|
||||
testSourceRangeIPv4 := "0.0.0.0/1"
|
||||
@@ -97,18 +93,22 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
testSourceRangeIPv6 := "2001:db8::/32"
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
service *v1.Service
|
||||
expected map[ServicePortName]*BaseServiceInfo
|
||||
isIPv6Mode *bool
|
||||
desc string
|
||||
service *v1.Service
|
||||
expected map[ServicePortName]*BaseServiceInfo
|
||||
ipFamily v1.IPFamily
|
||||
}{
|
||||
{
|
||||
desc: "nothing",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: nil,
|
||||
expected: map[ServicePortName]*BaseServiceInfo{},
|
||||
},
|
||||
{
|
||||
desc: "headless service",
|
||||
desc: "headless service",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: makeTestService("ns2", "headless", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeClusterIP
|
||||
svc.Spec.ClusterIP = v1.ClusterIPNone
|
||||
@@ -117,7 +117,9 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
expected: map[ServicePortName]*BaseServiceInfo{},
|
||||
},
|
||||
{
|
||||
desc: "headless sctp service",
|
||||
desc: "headless sctp service",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: makeTestService("ns2", "headless", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeClusterIP
|
||||
svc.Spec.ClusterIP = v1.ClusterIPNone
|
||||
@@ -126,7 +128,9 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
expected: map[ServicePortName]*BaseServiceInfo{},
|
||||
},
|
||||
{
|
||||
desc: "headless service without port",
|
||||
desc: "headless service without port",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: makeTestService("ns2", "headless-without-port", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeClusterIP
|
||||
svc.Spec.ClusterIP = v1.ClusterIPNone
|
||||
@@ -134,7 +138,9 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
expected: map[ServicePortName]*BaseServiceInfo{},
|
||||
},
|
||||
{
|
||||
desc: "cluster ip service",
|
||||
desc: "cluster ip service",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: makeTestService("ns2", "cluster-ip", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeClusterIP
|
||||
svc.Spec.ClusterIP = "172.16.55.4"
|
||||
@@ -147,7 +153,9 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nodeport service",
|
||||
desc: "nodeport service",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: makeTestService("ns2", "node-port", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeNodePort
|
||||
svc.Spec.ClusterIP = "172.16.55.10"
|
||||
@@ -160,7 +168,9 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "load balancer service",
|
||||
desc: "load balancer service",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: makeTestService("ns1", "load-balancer", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ClusterIP = "172.16.55.11"
|
||||
@@ -179,7 +189,9 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "load balancer service with only local traffic policy",
|
||||
desc: "load balancer service with only local traffic policy",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: makeTestService("ns1", "only-local-load-balancer", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
||||
svc.Spec.ClusterIP = "172.16.55.12"
|
||||
@@ -200,7 +212,9 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "external name service",
|
||||
desc: "external name service",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: makeTestService("ns2", "external-name", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeExternalName
|
||||
svc.Spec.ClusterIP = "172.16.55.4" // Should be ignored
|
||||
@@ -210,7 +224,9 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
expected: map[ServicePortName]*BaseServiceInfo{},
|
||||
},
|
||||
{
|
||||
desc: "service with ipv6 clusterIP under ipv4 mode, service should be filtered",
|
||||
desc: "service with ipv6 clusterIP under ipv4 mode, service should be filtered",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "invalidIPv6InIPV4Mode",
|
||||
@@ -235,10 +251,11 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
isIPv6Mode: &falseVal,
|
||||
},
|
||||
{
|
||||
desc: "service with ipv4 clusterIP under ipv6 mode, service should be filtered",
|
||||
desc: "service with ipv4 clusterIP under ipv6 mode, service should be filtered",
|
||||
ipFamily: v1.IPv6Protocol,
|
||||
|
||||
service: &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "invalidIPv4InIPV6Mode",
|
||||
@@ -263,10 +280,11 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
isIPv6Mode: &trueVal,
|
||||
},
|
||||
{
|
||||
desc: "service with ipv4 configurations under ipv4 mode",
|
||||
desc: "service with ipv4 configurations under ipv4 mode",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "validIPv4",
|
||||
@@ -300,10 +318,11 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
info.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: testExternalIPv4}}
|
||||
}),
|
||||
},
|
||||
isIPv6Mode: &falseVal,
|
||||
},
|
||||
{
|
||||
desc: "service with ipv6 configurations under ipv6 mode",
|
||||
desc: "service with ipv6 configurations under ipv6 mode",
|
||||
ipFamily: v1.IPv6Protocol,
|
||||
|
||||
service: &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "validIPv6",
|
||||
@@ -337,10 +356,11 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
info.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: testExternalIPv6}}
|
||||
}),
|
||||
},
|
||||
isIPv6Mode: &trueVal,
|
||||
},
|
||||
{
|
||||
desc: "service with both ipv4 and ipv6 configurations under ipv4 mode, ipv6 fields should be filtered",
|
||||
desc: "service with both ipv4 and ipv6 configurations under ipv4 mode, ipv6 fields should be filtered",
|
||||
ipFamily: v1.IPv4Protocol,
|
||||
|
||||
service: &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "filterIPv6InIPV4Mode",
|
||||
@@ -374,10 +394,11 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
info.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: testExternalIPv4}}
|
||||
}),
|
||||
},
|
||||
isIPv6Mode: &falseVal,
|
||||
},
|
||||
{
|
||||
desc: "service with both ipv4 and ipv6 configurations under ipv6 mode, ipv4 fields should be filtered",
|
||||
desc: "service with both ipv4 and ipv6 configurations under ipv6 mode, ipv4 fields should be filtered",
|
||||
ipFamily: v1.IPv6Protocol,
|
||||
|
||||
service: &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "filterIPv4InIPV6Mode",
|
||||
@@ -411,7 +432,6 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
info.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: testExternalIPv6}}
|
||||
}),
|
||||
},
|
||||
isIPv6Mode: &trueVal,
|
||||
},
|
||||
{
|
||||
desc: "service with extra space in LoadBalancerSourceRanges",
|
||||
@@ -437,21 +457,24 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
info.loadBalancerSourceRanges = []string{"10.1.2.0/28"}
|
||||
}),
|
||||
},
|
||||
isIPv6Mode: &falseVal,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
svcTracker.isIPv6Mode = tc.isIPv6Mode
|
||||
svcTracker := NewServiceChangeTracker(nil, tc.ipFamily, nil, nil)
|
||||
// outputs
|
||||
newServices := svcTracker.serviceToServiceMap(tc.service)
|
||||
|
||||
if len(newServices) != len(tc.expected) {
|
||||
t.Errorf("expected %d new, got %d: %v", len(tc.expected), len(newServices), spew.Sdump(newServices))
|
||||
t.Fatalf("expected %d new, got %d: %v", len(tc.expected), len(newServices), spew.Sdump(newServices))
|
||||
}
|
||||
for svcKey, expectedInfo := range tc.expected {
|
||||
svcInfo, _ := newServices[svcKey].(*BaseServiceInfo)
|
||||
svcInfo, exists := newServices[svcKey].(*BaseServiceInfo)
|
||||
if !exists {
|
||||
t.Fatalf("[%s] expected to find key %s", tc.desc, svcKey)
|
||||
}
|
||||
|
||||
if !svcInfo.clusterIP.Equal(expectedInfo.clusterIP) ||
|
||||
svcInfo.port != expectedInfo.port ||
|
||||
svcInfo.protocol != expectedInfo.protocol ||
|
||||
@@ -459,7 +482,19 @@ func TestServiceToServiceMap(t *testing.T) {
|
||||
!sets.NewString(svcInfo.externalIPs...).Equal(sets.NewString(expectedInfo.externalIPs...)) ||
|
||||
!sets.NewString(svcInfo.loadBalancerSourceRanges...).Equal(sets.NewString(expectedInfo.loadBalancerSourceRanges...)) ||
|
||||
!reflect.DeepEqual(svcInfo.loadBalancerStatus, expectedInfo.loadBalancerStatus) {
|
||||
t.Errorf("expected new[%v]to be %v, got %v", svcKey, expectedInfo, *svcInfo)
|
||||
t.Errorf("[%s] expected new[%v]to be %v, got %v", tc.desc, svcKey, expectedInfo, *svcInfo)
|
||||
}
|
||||
for svcKey, expectedInfo := range tc.expected {
|
||||
svcInfo, _ := newServices[svcKey].(*BaseServiceInfo)
|
||||
if !svcInfo.clusterIP.Equal(expectedInfo.clusterIP) ||
|
||||
svcInfo.port != expectedInfo.port ||
|
||||
svcInfo.protocol != expectedInfo.protocol ||
|
||||
svcInfo.healthCheckNodePort != expectedInfo.healthCheckNodePort ||
|
||||
!sets.NewString(svcInfo.externalIPs...).Equal(sets.NewString(expectedInfo.externalIPs...)) ||
|
||||
!sets.NewString(svcInfo.loadBalancerSourceRanges...).Equal(sets.NewString(expectedInfo.loadBalancerSourceRanges...)) ||
|
||||
!reflect.DeepEqual(svcInfo.loadBalancerStatus, expectedInfo.loadBalancerStatus) {
|
||||
t.Errorf("expected new[%v]to be %v, got %v", svcKey, expectedInfo, *svcInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -474,12 +509,12 @@ type FakeProxier struct {
|
||||
hostname string
|
||||
}
|
||||
|
||||
func newFakeProxier() *FakeProxier {
|
||||
func newFakeProxier(ipFamily v1.IPFamily) *FakeProxier {
|
||||
return &FakeProxier{
|
||||
serviceMap: make(ServiceMap),
|
||||
serviceChanges: NewServiceChangeTracker(nil, nil, nil, nil),
|
||||
serviceChanges: NewServiceChangeTracker(nil, ipFamily, nil, nil),
|
||||
endpointsMap: make(EndpointsMap),
|
||||
endpointsChanges: NewEndpointChangeTracker(testHostname, nil, nil, nil, false, nil),
|
||||
endpointsChanges: NewEndpointChangeTracker(testHostname, nil, ipFamily, nil, false, nil),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +537,7 @@ func (fake *FakeProxier) deleteService(service *v1.Service) {
|
||||
}
|
||||
|
||||
func TestUpdateServiceMapHeadless(t *testing.T) {
|
||||
fp := newFakeProxier()
|
||||
fp := newFakeProxier(v1.IPv4Protocol)
|
||||
|
||||
makeServiceMap(fp,
|
||||
makeTestService("ns2", "headless", func(svc *v1.Service) {
|
||||
@@ -533,7 +568,7 @@ func TestUpdateServiceMapHeadless(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateServiceTypeExternalName(t *testing.T) {
|
||||
fp := newFakeProxier()
|
||||
fp := newFakeProxier(v1.IPv4Protocol)
|
||||
|
||||
makeServiceMap(fp,
|
||||
makeTestService("ns2", "external-name", func(svc *v1.Service) {
|
||||
@@ -558,7 +593,7 @@ func TestUpdateServiceTypeExternalName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildServiceMapAddRemove(t *testing.T) {
|
||||
fp := newFakeProxier()
|
||||
fp := newFakeProxier(v1.IPv4Protocol)
|
||||
|
||||
services := []*v1.Service{
|
||||
makeTestService("ns2", "cluster-ip", func(svc *v1.Service) {
|
||||
@@ -661,7 +696,7 @@ func TestBuildServiceMapAddRemove(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildServiceMapServiceUpdate(t *testing.T) {
|
||||
fp := newFakeProxier()
|
||||
fp := newFakeProxier(v1.IPv4Protocol)
|
||||
|
||||
servicev1 := makeTestService("ns1", "svc1", func(svc *v1.Service) {
|
||||
svc.Spec.Type = v1.ServiceTypeClusterIP
|
||||
|
Reference in New Issue
Block a user