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

@@ -29,15 +29,16 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme"
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer"
// ensure types are installed
_ "k8s.io/kubernetes/pkg/apis/core/install"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
)
// TestWorkloadDefaults detects changes to defaults within PodTemplateSpec.
@@ -1023,140 +1024,6 @@ func TestSetDefaultService(t *testing.T) {
}
}
func TestSetDefaultServiceIPFamily(t *testing.T) {
svc := v1.Service{
Spec: v1.ServiceSpec{
SessionAffinity: v1.ServiceAffinityNone,
Type: v1.ServiceTypeClusterIP,
},
}
testCases := []struct {
name string
inSvcTweak func(s v1.Service) v1.Service
outSvcTweak func(s v1.Service) v1.Service
enableDualStack bool
}{
{
name: "dualstack off. ipfamily not set",
inSvcTweak: func(s v1.Service) v1.Service { return s },
outSvcTweak: func(s v1.Service) v1.Service { return s },
enableDualStack: false,
},
{
name: "dualstack on. ipfamily not set, service is *not* ClusterIP-able",
inSvcTweak: func(s v1.Service) v1.Service {
s.Spec.Type = v1.ServiceTypeExternalName
return s
},
outSvcTweak: func(s v1.Service) v1.Service { return s },
enableDualStack: true,
},
{
name: "dualstack off. ipfamily set",
inSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
return s
},
enableDualStack: false,
},
{
name: "dualstack off. ipfamily not set. clusterip set",
inSvcTweak: func(s v1.Service) v1.Service {
s.Spec.ClusterIP = "1.1.1.1"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
return s
},
enableDualStack: false,
},
{
name: "dualstack on. ipfamily not set (clusterIP is v4)",
inSvcTweak: func(s v1.Service) v1.Service {
s.Spec.ClusterIP = "1.1.1.1"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
return s
},
enableDualStack: true,
},
{
name: "dualstack on. ipfamily not set (clusterIP is v6)",
inSvcTweak: func(s v1.Service) v1.Service {
s.Spec.ClusterIP = "fdd7:7713:8917:77ed:ffff:ffff:ffff:ffff"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv6Service := v1.IPv6Protocol
s.Spec.IPFamily = &ipv6Service
return s
},
enableDualStack: true,
},
{
name: "dualstack on. ipfamily set (clusterIP is v4)",
inSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
s.Spec.ClusterIP = "1.1.1.1"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
return s
},
enableDualStack: true,
},
{
name: "dualstack on. ipfamily set (clusterIP is v6)",
inSvcTweak: func(s v1.Service) v1.Service {
ipv6Service := v1.IPv6Protocol
s.Spec.IPFamily = &ipv6Service
s.Spec.ClusterIP = "fdd7:7713:8917:77ed:ffff:ffff:ffff:ffff"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv6Service := v1.IPv6Protocol
s.Spec.IPFamily = &ipv6Service
return s
},
enableDualStack: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
tweakedIn := tc.inSvcTweak(svc)
expectedSvc := tc.outSvcTweak(svc)
defaulted := roundTrip(t, runtime.Object(&tweakedIn))
defaultedSvc := defaulted.(*v1.Service)
if expectedSvc.Spec.IPFamily != nil {
if defaultedSvc.Spec.IPFamily == nil {
t.Fatalf("defaulted service ipfamily is nil while expected is not")
}
if *(expectedSvc.Spec.IPFamily) != *(defaultedSvc.Spec.IPFamily) {
t.Fatalf("defaulted service ipfamily %v does not match expected %v", defaultedSvc.Spec.IPFamily, expectedSvc.Spec.IPFamily)
}
}
if expectedSvc.Spec.IPFamily == nil && defaultedSvc.Spec.IPFamily != nil {
t.Fatalf("defaulted service ipfamily is not nil, while expected service ipfamily is")
}
})
}
}
func TestSetDefaultServiceSessionAffinityConfig(t *testing.T) {
testCases := map[string]v1.Service{
"SessionAffinityConfig is empty": {
@@ -1935,3 +1802,253 @@ func TestSetDefaultEnableServiceLinks(t *testing.T) {
t.Errorf("Expected enableServiceLinks value: %+v\ngot: %+v\n", v1.DefaultEnableServiceLinks, *output.Spec.EnableServiceLinks)
}
}
func TestSetDefaultIPFamilies(t *testing.T) {
preferDualStack := v1.IPFamilyPolicyPreferDualStack
requireDualStack := v1.IPFamilyPolicyRequireDualStack
testCases := []struct {
name string
expectedIPFamiliesWithGate []v1.IPFamily
svc v1.Service
}{
{
name: "must not set for ExternalName",
expectedIPFamiliesWithGate: nil,
svc: v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeExternalName,
},
},
},
{
name: "must not set for single stack",
expectedIPFamiliesWithGate: nil,
svc: v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
},
},
},
{
name: "must not set for single stack, even when a family is set",
expectedIPFamiliesWithGate: []v1.IPFamily{v1.IPv6Protocol},
svc: v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
IPFamilies: []v1.IPFamily{v1.IPv6Protocol},
},
},
},
{
name: "must not set for preferDualStack",
expectedIPFamiliesWithGate: []v1.IPFamily{v1.IPv6Protocol},
svc: v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
IPFamilyPolicy: &preferDualStack,
IPFamilies: []v1.IPFamily{v1.IPv6Protocol},
},
},
},
{
name: "must set for requireDualStack (6,4)",
expectedIPFamiliesWithGate: []v1.IPFamily{v1.IPv6Protocol, v1.IPv4Protocol},
svc: v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
IPFamilyPolicy: &requireDualStack,
IPFamilies: []v1.IPFamily{v1.IPv6Protocol},
},
},
},
{
name: "must set for requireDualStack (4,6)",
expectedIPFamiliesWithGate: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
svc: v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
IPFamilyPolicy: &requireDualStack,
IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
},
},
},
}
for _, test := range testCases {
// run with gate
t.Run(test.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
obj2 := roundTrip(t, runtime.Object(&test.svc))
svc2 := obj2.(*v1.Service)
if len(test.expectedIPFamiliesWithGate) != len(svc2.Spec.IPFamilies) {
t.Fatalf("expected .spec.IPFamilies len:%v got %v", len(test.expectedIPFamiliesWithGate), len(svc2.Spec.IPFamilies))
}
for i, family := range test.expectedIPFamiliesWithGate {
if svc2.Spec.IPFamilies[i] != family {
t.Fatalf("failed. expected family %v at %v got %v", family, i, svc2.Spec.IPFamilies[i])
}
}
})
// run without gate (families should not change)
t.Run(fmt.Sprintf("without-gate:%s", test.name), func(t *testing.T) {
obj2 := roundTrip(t, runtime.Object(&test.svc))
svc2 := obj2.(*v1.Service)
if len(test.svc.Spec.IPFamilies) != len(svc2.Spec.IPFamilies) {
t.Fatalf("expected .spec.IPFamilies len:%v got %v", len(test.expectedIPFamiliesWithGate), len(svc2.Spec.IPFamilies))
}
for i, family := range test.svc.Spec.IPFamilies {
if svc2.Spec.IPFamilies[i] != family {
t.Fatalf("failed. expected family %v at %v got %v", family, i, svc2.Spec.IPFamilies[i])
}
}
})
}
}
func TestSetDefaultServiceIPFamilyPolicy(t *testing.T) {
singleStack := v1.IPFamilyPolicySingleStack
preferDualStack := v1.IPFamilyPolicyPreferDualStack
requireDualStack := v1.IPFamilyPolicyRequireDualStack
testCases := []struct {
name string
expectedIPfamilyPolicy *v1.IPFamilyPolicyType
svc v1.Service
}{
{
name: "must not set for ExternalName",
expectedIPfamilyPolicy: nil,
svc: v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeExternalName,
},
},
},
{
name: "must not set for ExternalName even with semantically valid data",
expectedIPfamilyPolicy: nil,
svc: v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeExternalName,
ClusterIPs: []string{"1.2.3.4", "2001::1"},
IPFamilies: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
},
},
},
{
name: "must set if there are more than one ip",
expectedIPfamilyPolicy: &requireDualStack,
svc: v1.Service{
Spec: v1.ServiceSpec{
ClusterIPs: []string{"1.2.3.4", "2001::1"},
},
},
},
{
name: "must set if there are more than one ip family",
expectedIPfamilyPolicy: &requireDualStack,
svc: v1.Service{
Spec: v1.ServiceSpec{
IPFamilies: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
},
},
},
{
name: "must not set if there is one ip",
expectedIPfamilyPolicy: nil,
svc: v1.Service{
Spec: v1.ServiceSpec{
ClusterIPs: []string{"1.2.3.4"},
},
},
},
{
name: "must not set if there is one ip family",
expectedIPfamilyPolicy: nil,
svc: v1.Service{
Spec: v1.ServiceSpec{
IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
},
},
},
{
name: "must not override user input",
expectedIPfamilyPolicy: &singleStack,
svc: v1.Service{
Spec: v1.ServiceSpec{
IPFamilyPolicy: &singleStack,
},
},
},
{
name: "must not override user input/ preferDualStack",
expectedIPfamilyPolicy: &preferDualStack,
svc: v1.Service{
Spec: v1.ServiceSpec{
IPFamilyPolicy: &preferDualStack,
},
},
},
{
name: "must not override user input even when it is invalid",
expectedIPfamilyPolicy: &preferDualStack,
svc: v1.Service{
Spec: v1.ServiceSpec{
IPFamilies: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
IPFamilyPolicy: &preferDualStack,
},
},
},
}
for _, test := range testCases {
// with gate
t.Run(test.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
obj2 := roundTrip(t, runtime.Object(&test.svc))
svc2 := obj2.(*v1.Service)
if test.expectedIPfamilyPolicy == nil && svc2.Spec.IPFamilyPolicy != nil {
t.Fatalf("expected .spec.PreferDualStack to be unset (nil)")
}
if test.expectedIPfamilyPolicy != nil && (svc2.Spec.IPFamilyPolicy == nil || *(svc2.Spec.IPFamilyPolicy) != *(test.expectedIPfamilyPolicy)) {
t.Fatalf("expected .spec.PreferDualStack to be set to %v got %v", *(test.expectedIPfamilyPolicy), svc2.Spec.IPFamilyPolicy)
}
})
// without gate. IPFamilyPolicy should never change
t.Run(test.name, func(t *testing.T) {
obj2 := roundTrip(t, runtime.Object(&test.svc))
svc2 := obj2.(*v1.Service)
if test.svc.Spec.IPFamilyPolicy == nil && svc2.Spec.IPFamilyPolicy != nil {
t.Fatalf("expected .spec.PreferDualStack to be unset (nil)")
}
if test.svc.Spec.IPFamilyPolicy != nil && (svc2.Spec.IPFamilyPolicy == nil || *(svc2.Spec.IPFamilyPolicy) != *(test.expectedIPfamilyPolicy)) {
t.Fatalf("expected .spec.PreferDualStack to be set to %v got %v", *(test.expectedIPfamilyPolicy), svc2.Spec.IPFamilyPolicy)
}
})
}
}