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:
Khaled Henidak (Kal)
2020-10-26 13:15:59 -07:00
committed by GitHub
parent d0e06cf3e0
commit 6675eba3ef
84 changed files with 11170 additions and 3514 deletions

View File

@@ -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