move IPv6DualStack feature to stable. (#104691)

* kube-proxy

* endpoints controller

* app: kube-controller-manager

* app: cloud-controller-manager

* kubelet

* app: api-server

* node utils + registry/strategy

* api: validation (comment removal)

* api:pod strategy (util pkg)

* api: docs

* core: integration testing

* kubeadm: change feature gate to GA

* service registry and rest stack

* move feature to GA

* generated
This commit is contained in:
Khaled Henidak (Kal)
2021-09-24 16:30:22 -07:00
committed by GitHub
parent c74d799677
commit a53e2eaeab
42 changed files with 455 additions and 1373 deletions

View File

@@ -490,32 +490,16 @@ func DropDisabledPodFields(pod, oldPod *api.Pod) {
podAnnotations map[string]string
oldPodSpec *api.PodSpec
oldPodAnnotations map[string]string
podStatus *api.PodStatus
oldPodStatus *api.PodStatus
)
if pod != nil {
podSpec = &pod.Spec
podAnnotations = pod.Annotations
podStatus = &pod.Status
}
if oldPod != nil {
oldPodSpec = &oldPod.Spec
oldPodAnnotations = oldPod.Annotations
oldPodStatus = &oldPod.Status
}
dropDisabledFields(podSpec, podAnnotations, oldPodSpec, oldPodAnnotations)
dropPodStatusDisabledFields(podStatus, oldPodStatus)
}
// dropPodStatusDisabledFields removes disabled fields from the pod status
func dropPodStatusDisabledFields(podStatus *api.PodStatus, oldPodStatus *api.PodStatus) {
// trim PodIPs down to only one entry (non dual stack).
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) &&
!multiplePodIPsInUse(oldPodStatus) {
if len(podStatus.PodIPs) != 0 {
podStatus.PodIPs = podStatus.PodIPs[0:1]
}
}
}
// dropDisabledFields removes disabled fields from the pod metadata and spec.
@@ -824,17 +808,6 @@ func ephemeralInUse(podSpec *api.PodSpec) bool {
return false
}
// podPriorityInUse returns true if status is not nil and number of PodIPs is greater than one
func multiplePodIPsInUse(podStatus *api.PodStatus) bool {
if podStatus == nil {
return false
}
if len(podStatus.PodIPs) > 1 {
return true
}
return false
}
// SeccompAnnotationForField takes a pod seccomp profile field and returns the
// converted annotation value
func SeccompAnnotationForField(field *api.SeccompProfile) string {

View File

@@ -1053,107 +1053,6 @@ func TestDropProbeGracePeriod(t *testing.T) {
}
}
// helper creates a podStatus with list of PodIPs
func makePodStatus(podIPs []api.PodIP) *api.PodStatus {
return &api.PodStatus{
PodIPs: podIPs,
}
}
func TestDropStatusPodIPs(t *testing.T) {
testCases := []struct {
name string
podStatus *api.PodStatus
oldPodStatus *api.PodStatus
comparePodStatus *api.PodStatus
enableDualStack bool
}{
{
name: "nil pod ips",
enableDualStack: false,
podStatus: makePodStatus(nil),
oldPodStatus: nil,
comparePodStatus: makePodStatus(nil),
},
{
name: "empty pod ips",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{}),
oldPodStatus: nil,
comparePodStatus: makePodStatus([]api.PodIP{}),
},
{
name: "single family ipv6",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}}),
},
{
name: "single family ipv4",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "1.1.1.1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "1.1.1.1"}}),
},
{
name: "dualstack 4-6",
enableDualStack: true,
podStatus: makePodStatus([]api.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
},
{
name: "dualstack 6-4",
enableDualStack: true,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
},
{
name: "not dualstack 6-4=>4only",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
oldPodStatus: nil,
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}}),
},
{
name: "not dualstack 6-4=>as is (used in old)",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
oldPodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
},
{
name: "not dualstack 6-4=>6only",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
oldPodStatus: nil,
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}}),
},
{
name: "not dualstack 6-4=>as is (used in old)",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
oldPodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
},
}
for _, tc := range testCases {
func() {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
dropPodStatusDisabledFields(tc.podStatus, tc.oldPodStatus)
old := tc.oldPodStatus.DeepCopy()
// old pod status should never be changed
if !reflect.DeepEqual(tc.oldPodStatus, old) {
t.Errorf("%v: old pod status changed: %v", tc.name, cmp.Diff(tc.oldPodStatus, old))
}
if !reflect.DeepEqual(tc.podStatus, tc.comparePodStatus) {
t.Errorf("%v: unexpected pod status: %v", tc.name, cmp.Diff(tc.podStatus, tc.comparePodStatus))
}
}()
}
}
func TestDropEphemeralContainers(t *testing.T) {
podWithEphemeralContainers := func() *api.Pod {
return &api.Pod{

View File

@@ -2286,8 +2286,6 @@ var validEnvDownwardAPIFieldPathExpressions = sets.NewString(
"spec.serviceAccountName",
"status.hostIP",
"status.podIP",
// status.podIPs is populated even if IPv6DualStack feature gate
// is not enabled. This will work for single stack and dual stack.
"status.podIPs")
var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage")

View File

@@ -30,7 +30,6 @@ import (
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
coreinformers "k8s.io/client-go/informers/core/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
@@ -48,7 +47,6 @@ import (
helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/controller"
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
"k8s.io/kubernetes/pkg/features"
utillabels "k8s.io/kubernetes/pkg/util/labels"
utilnet "k8s.io/utils/net"
)
@@ -229,42 +227,38 @@ func podToEndpointAddressForService(svc *v1.Service, pod *v1.Pod) (*v1.EndpointA
var endpointIP string
ipFamily := v1.IPv4Protocol
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
// In a legacy cluster, the pod IP is guaranteed to be usable
endpointIP = pod.Status.PodIP
if len(svc.Spec.IPFamilies) > 0 {
// controller is connected to an api-server that correctly sets IPFamilies
ipFamily = svc.Spec.IPFamilies[0] // this works for headful and headless
} else {
//feature flag enabled and pods may have multiple IPs
if len(svc.Spec.IPFamilies) > 0 {
// controller is connected to an api-server that correctly sets IPFamilies
ipFamily = svc.Spec.IPFamilies[0] // this works for headful and headless
// controller is connected to an api server that does not correctly
// set IPFamilies (e.g. old api-server during an upgrade)
// TODO (khenidak): remove by when the possibility of upgrading
// from a cluster that does not support dual stack is nil
if len(svc.Spec.ClusterIP) > 0 && svc.Spec.ClusterIP != v1.ClusterIPNone {
// headful service. detect via service clusterIP
if utilnet.IsIPv6String(svc.Spec.ClusterIP) {
ipFamily = v1.IPv6Protocol
}
} else {
// controller is connected to an api server that does not correctly
// set IPFamilies (e.g. old api-server during an upgrade)
if len(svc.Spec.ClusterIP) > 0 && svc.Spec.ClusterIP != v1.ClusterIPNone {
// headful service. detect via service clusterIP
if utilnet.IsIPv6String(svc.Spec.ClusterIP) {
ipFamily = v1.IPv6Protocol
}
} else {
// Since this is a headless service we use podIP to identify the family.
// This assumes that status.PodIP is assigned correctly (follows pod cidr and
// pod cidr list order is same as service cidr list order). The expectation is
// this is *most probably* the case.
// Since this is a headless service we use podIP to identify the family.
// This assumes that status.PodIP is assigned correctly (follows pod cidr and
// pod cidr list order is same as service cidr list order). The expectation is
// this is *most probably* the case.
// if the family was incorrectly identified then this will be corrected once the
// the upgrade is completed (controller connects to api-server that correctly defaults services)
if utilnet.IsIPv6String(pod.Status.PodIP) {
ipFamily = v1.IPv6Protocol
}
// if the family was incorrectly identified then this will be corrected once the
// the upgrade is completed (controller connects to api-server that correctly defaults services)
if utilnet.IsIPv6String(pod.Status.PodIP) {
ipFamily = v1.IPv6Protocol
}
}
}
// find an ip that matches the family
for _, podIP := range pod.Status.PodIPs {
if (ipFamily == v1.IPv6Protocol) == utilnet.IsIPv6String(podIP.IP) {
endpointIP = podIP.IP
break
}
// find an ip that matches the family
for _, podIP := range pod.Status.PodIPs {
if (ipFamily == v1.IPv6Protocol) == utilnet.IsIPv6String(podIP.IP) {
endpointIP = podIP.IP
break
}
}

View File

@@ -33,7 +33,6 @@ import (
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
@@ -41,11 +40,9 @@ import (
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
utiltesting "k8s.io/client-go/util/testing"
featuregatetesting "k8s.io/component-base/featuregate/testing"
endptspkg "k8s.io/kubernetes/pkg/api/v1/endpoints"
api "k8s.io/kubernetes/pkg/apis/core"
controllerpkg "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/features"
utilnet "k8s.io/utils/net"
utilpointer "k8s.io/utils/pointer"
)
@@ -1207,209 +1204,154 @@ func TestPodToEndpointAddressForService(t *testing.T) {
ipv6 := v1.IPv6Protocol
testCases := []struct {
name string
enableDualStack bool
ipFamilies []v1.IPFamily
service v1.Service
name string
ipFamilies []v1.IPFamily
service v1.Service
expectedEndpointFamily v1.IPFamily
expectError bool
}{
{
name: "v4 service, in a single stack cluster",
enableDualStack: false,
ipFamilies: ipv4only,
name: "v4 service, in a single stack cluster",
ipFamilies: ipv4only,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1",
},
},
expectedEndpointFamily: ipv4,
},
{
name: "v4 service, in a dual stack cluster",
enableDualStack: true,
ipFamilies: ipv4ipv6,
name: "v4 service, in a dual stack cluster",
ipFamilies: ipv4ipv6,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1",
},
},
expectedEndpointFamily: ipv4,
},
{
name: "v4 service, in a dual stack ipv6-primary cluster",
enableDualStack: true,
ipFamilies: ipv6ipv4,
name: "v4 service, in a dual stack ipv6-primary cluster",
ipFamilies: ipv6ipv4,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1",
},
},
expectedEndpointFamily: ipv4,
},
{
name: "v4 headless service, in a single stack cluster",
enableDualStack: false,
ipFamilies: ipv4only,
name: "v4 headless service, in a single stack cluster",
ipFamilies: ipv4only,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone,
},
},
expectedEndpointFamily: ipv4,
},
{
name: "v4 headless service, in a dual stack cluster",
enableDualStack: false,
ipFamilies: ipv4ipv6,
name: "v4 headless service, in a dual stack cluster",
ipFamilies: ipv4ipv6,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone,
IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
},
},
expectedEndpointFamily: ipv4,
},
{
name: "v4 legacy headless service, in a dual stack cluster",
enableDualStack: false,
ipFamilies: ipv4ipv6,
name: "v4 legacy headless service, in a dual stack cluster",
ipFamilies: ipv4ipv6,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone,
},
},
expectedEndpointFamily: ipv4,
},
{
name: "v4 legacy headless service, in a dual stack ipv6-primary cluster",
enableDualStack: true,
ipFamilies: ipv6ipv4,
name: "v4 legacy headless service, in a dual stack ipv6-primary cluster",
ipFamilies: ipv6ipv4,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone,
},
},
expectedEndpointFamily: ipv6,
},
{
name: "v6 service, in a dual stack cluster",
enableDualStack: true,
ipFamilies: ipv4ipv6,
name: "v6 service, in a dual stack cluster",
ipFamilies: ipv4ipv6,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: "3000::1",
},
},
expectedEndpointFamily: ipv6,
},
{
name: "v6 headless service, in a single stack cluster",
enableDualStack: false,
ipFamilies: ipv6only,
name: "v6 headless service, in a single stack cluster",
ipFamilies: ipv6only,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone,
},
},
expectedEndpointFamily: ipv6,
},
{
name: "v6 headless service, in a dual stack cluster (connected to a new api-server)",
enableDualStack: true,
ipFamilies: ipv4ipv6,
name: "v6 headless service, in a dual stack cluster (connected to a new api-server)",
ipFamilies: ipv4ipv6,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone,
IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, // <- set by a api-server defaulting logic
},
},
expectedEndpointFamily: ipv6,
},
{
name: "v6 legacy headless service, in a dual stack cluster (connected to a old api-server)",
enableDualStack: false,
ipFamilies: ipv4ipv6,
name: "v6 legacy headless service, in a dual stack cluster (connected to a old api-server)",
ipFamilies: ipv4ipv6,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone, // <- families are not set by api-server
},
},
expectedEndpointFamily: ipv4,
},
// in reality this is a misconfigured cluster
// i.e user is not using dual stack and have PodIP == v4 and ServiceIP==v6
// we are testing that we will keep producing the expected behavior
// previously controller could assign wrong ip to endpoint address
// with gate removed. this is no longer the case. this is *not* behavior change
// because previously things would have failed in kube-proxy anyway (due to editing wrong iptables).
{
name: "v6 service, in a v4 only cluster. dual stack disabled",
enableDualStack: false,
ipFamilies: ipv4only,
name: "v6 service, in a v4 only cluster.",
ipFamilies: ipv4only,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: "3000::1",
},
},
expectError: true,
expectedEndpointFamily: ipv4,
},
// but this will actually give an error
{
name: "v6 service, in a v4 only cluster - dual stack enabled",
enableDualStack: true,
ipFamilies: ipv4only,
name: "v6 service, in a v4 only cluster",
ipFamilies: ipv4only,
service: v1.Service{
Spec: v1.ServiceSpec{
ClusterIP: "3000::1",
},
},
expectError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
podStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
ns := "test"
addPods(podStore, ns, 1, 1, 0, tc.ipFamilies)

View File

@@ -796,7 +796,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha},
NonPreemptingPriority: {Default: true, PreRelease: featuregate.Beta},
PodOverhead: {Default: true, PreRelease: featuregate.Beta},
IPv6DualStack: {Default: true, PreRelease: featuregate.Beta},
IPv6DualStack: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
EndpointSlice: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
EndpointSliceProxying: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
EndpointSliceTerminatingCondition: {Default: true, PreRelease: featuregate.Beta},

View File

@@ -369,3 +369,7 @@ func (f *fakeIPTables) isBuiltinChain(tableName utiliptables.Table, chainName ut
func (f *fakeIPTables) HasRandomFully() bool {
return false
}
func (f *fakeIPTables) Present() bool {
return true
}

View File

@@ -47,8 +47,6 @@ import (
utilexec "k8s.io/utils/exec"
utilebtables "k8s.io/utils/net/ebtables"
utilfeature "k8s.io/apiserver/pkg/util/feature"
kubefeatures "k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net"
)
@@ -253,12 +251,6 @@ func (plugin *kubenetNetworkPlugin) Event(name string, details map[string]interf
klog.V(4).InfoS("Kubenet: PodCIDR is set to new value", "podCIDR", podCIDR)
podCIDRs := strings.Split(podCIDR, ",")
// reset to one cidr if dual stack is not enabled
if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.IPv6DualStack) && len(podCIDRs) > 1 {
klog.V(2).InfoS("This node has multiple pod cidrs assigned and dual stack is not enabled. ignoring all except first cidr")
podCIDRs = podCIDRs[0:1]
}
for idx, currentPodCIDR := range podCIDRs {
_, cidr, err := netutils.ParseCIDRSloppy(currentPodCIDR)
if nil != err {

View File

@@ -39,9 +39,6 @@ import (
"k8s.io/kubernetes/pkg/kubelet/dockershim/network/metrics"
utilexec "k8s.io/utils/exec"
netutils "k8s.io/utils/net"
utilfeature "k8s.io/apiserver/pkg/util/feature"
kubefeatures "k8s.io/kubernetes/pkg/features"
)
const (
@@ -263,19 +260,6 @@ func getOnePodIP(execer utilexec.Interface, nsenterPath, netnsPath, interfaceNam
// TODO (khenidak). The "primary ip" in dual stack world does not really exist. For now
// we are defaulting to v4 as primary
func GetPodIPs(execer utilexec.Interface, nsenterPath, netnsPath, interfaceName string) ([]net.IP, error) {
if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.IPv6DualStack) {
ip, err := getOnePodIP(execer, nsenterPath, netnsPath, interfaceName, "-4")
if err != nil {
// Fall back to IPv6 address if no IPv4 address is present
ip, err = getOnePodIP(execer, nsenterPath, netnsPath, interfaceName, "-6")
}
if err != nil {
return nil, err
}
return []net.IP{ip}, nil
}
var (
list []net.IP
errs []error

View File

@@ -24,9 +24,7 @@ import (
"time"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
utilexec "k8s.io/utils/exec"
utilnet "k8s.io/utils/net"
@@ -34,21 +32,23 @@ import (
func (kl *Kubelet) initNetworkUtil() {
exec := utilexec.New()
// At this point in startup we don't know the actual node IPs, so we configure dual stack iptables
// rules if the node _might_ be dual-stack, and single-stack based on requested nodeIPs[0] otherwise.
maybeDualStack := utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack)
// TODO: @khenidak review when there is no IPv6 iptables exec what should happen here (note: no error returned from this func)
ipv6Primary := kl.nodeIPs != nil && utilnet.IsIPv6(kl.nodeIPs[0])
var iptClients []utiliptables.Interface
var protocols []utiliptables.Protocol
if maybeDualStack || !ipv6Primary {
protocols = append(protocols, utiliptables.ProtocolIPv4)
iptClients = append(iptClients, utiliptables.New(exec, utiliptables.ProtocolIPv4))
}
if maybeDualStack || ipv6Primary {
protocols = append(protocols, utiliptables.ProtocolIPv6)
iptClients = append(iptClients, utiliptables.New(exec, utiliptables.ProtocolIPv6))
// assume 4,6
protocols = append(protocols, utiliptables.ProtocolIPv4)
iptClients = append(iptClients, utiliptables.New(exec, utiliptables.ProtocolIPv4))
protocols = append(protocols, utiliptables.ProtocolIPv6)
iptClients = append(iptClients, utiliptables.New(exec, utiliptables.ProtocolIPv6))
// and if they are not
if ipv6Primary {
protocols[0], protocols[1] = protocols[1], protocols[0]
iptClients[0], iptClients[1] = iptClients[1], iptClients[0]
}
for i := range iptClients {

View File

@@ -1508,7 +1508,7 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po
if kubecontainer.IsHostNetworkPod(pod) && s.PodIP == "" {
s.PodIP = hostIPs[0].String()
s.PodIPs = []v1.PodIP{{IP: s.PodIP}}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && len(hostIPs) == 2 {
if len(hostIPs) == 2 {
s.PodIPs = append(s.PodIPs, v1.PodIP{IP: hostIPs[1].String()})
}
}

View File

@@ -38,10 +38,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff"
utilfeature "k8s.io/apiserver/pkg/util/feature"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/record"
featuregatetesting "k8s.io/component-base/featuregate/testing"
netutils "k8s.io/utils/net"
// TODO: remove this import if
@@ -49,7 +47,6 @@ import (
// to "v1"?
_ "k8s.io/kubernetes/pkg/apis/core/install"
"k8s.io/kubernetes/pkg/features"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
"k8s.io/kubernetes/pkg/kubelet/cri/streaming/portforward"
@@ -3113,7 +3110,6 @@ func TestTruncatePodHostname(t *testing.T) {
func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
testcases := []struct {
name string
dualStack bool
nodeAddresses []v1.NodeAddress
criPodIPs []string
podIPs []v1.PodIP
@@ -3137,22 +3133,11 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{IP: "10.0.0.1"},
},
},
{
name: "Dual-stack addresses are ignored in single-stack cluster",
nodeAddresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
},
podIPs: []v1.PodIP{
{IP: "10.0.0.1"},
},
},
{
name: "Single-stack addresses in dual-stack cluster",
nodeAddresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
},
dualStack: true,
podIPs: []v1.PodIP{
{IP: "10.0.0.1"},
},
@@ -3164,7 +3149,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.2"},
{Type: v1.NodeExternalIP, Address: "192.168.0.1"},
},
dualStack: true,
podIPs: []v1.PodIP{
{IP: "10.0.0.1"},
},
@@ -3175,7 +3159,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
},
dualStack: true,
podIPs: []v1.PodIP{
{IP: "10.0.0.1"},
{IP: "fd01::1234"},
@@ -3187,7 +3170,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
},
dualStack: true,
criPodIPs: []string{"192.168.0.1"},
podIPs: []v1.PodIP{
{IP: "192.168.0.1"},
@@ -3199,7 +3181,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
},
dualStack: true,
criPodIPs: []string{"192.168.0.1", "2001:db8::2"},
podIPs: []v1.PodIP{
{IP: "192.168.0.1"},
@@ -3213,7 +3194,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
},
dualStack: true,
criPodIPs: []string{"2001:db8::2", "192.168.0.1"},
podIPs: []v1.PodIP{
{IP: "192.168.0.1"},
@@ -3228,8 +3208,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
defer testKubelet.Cleanup()
kl := testKubelet.kubelet
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.dualStack)()
kl.nodeLister = testNodeLister{nodes: []*v1.Node{
{
ObjectMeta: metav1.ObjectMeta{Name: string(kl.nodeName)},

View File

@@ -30,7 +30,6 @@ import (
componentbaseconfig "k8s.io/component-base/config"
"k8s.io/component-base/metrics"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
kubefeatures "k8s.io/kubernetes/pkg/features"
kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config"
netutils "k8s.io/utils/net"
)
@@ -75,22 +74,19 @@ func Validate(config *kubeproxyconfig.KubeProxyConfiguration) field.ErrorList {
}
allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...)
dualStackEnabled := effectiveFeatures.Enabled(kubefeatures.IPv6DualStack)
if config.ClusterCIDR != "" {
cidrs := strings.Split(config.ClusterCIDR, ",")
switch {
// if DualStack only valid one cidr or two cidrs with one of each IP family
case dualStackEnabled && len(cidrs) > 2:
case len(cidrs) > 2:
allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)"))
// if DualStack and two cidrs validate if there is at least one of each IP family
case dualStackEnabled && len(cidrs) == 2:
case len(cidrs) == 2:
isDual, err := netutils.IsDualStackCIDRStrings(cidrs)
if err != nil || !isDual {
allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)"))
}
// if not DualStack only one CIDR allowed
case !dualStackEnabled && len(cidrs) > 1:
case len(cidrs) > 1:
allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)"))
// if we are here means that len(cidrs) == 1, we need to validate it
default:

View File

@@ -122,7 +122,6 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249",
FeatureGates: map[string]bool{"IPv6DualStack": true},
ClusterCIDR: "192.168.59.0/24",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@@ -142,7 +141,6 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249",
FeatureGates: map[string]bool{"IPv6DualStack": true},
ClusterCIDR: "fd00:192:168::/64",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@@ -162,7 +160,6 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249",
FeatureGates: map[string]bool{"IPv6DualStack": true},
ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@@ -279,36 +276,11 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
},
expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0", "must be a valid CIDR block (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")},
},
"Two ClusterCIDR addresses provided without DualStack feature-enabled": {
config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249",
// DualStack ClusterCIDR without feature flag enabled
FeatureGates: map[string]bool{"IPv6DualStack": false},
ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
},
Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
},
},
expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0/24,fd00:192:168::/64", "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")},
},
"Invalid number of ClusterCIDRs": {
config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249",
FeatureGates: map[string]bool{"IPv6DualStack": true},
ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@@ -396,16 +368,18 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
},
}
for _, testCase := range testCases {
errs := Validate(&testCase.config)
if len(testCase.expectedErrs) != len(errs) {
t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
}
for i, err := range errs {
if err.Error() != testCase.expectedErrs[i].Error() {
t.Fatalf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
errs := Validate(&testCase.config)
if len(testCase.expectedErrs) != len(errs) {
t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
}
}
for i, err := range errs {
if err.Error() != testCase.expectedErrs[i].Error() {
t.Fatalf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
}
}
})
}
}

View File

@@ -180,11 +180,6 @@ type StackCompatTester interface {
type DualStackCompatTester struct{}
func (t DualStackCompatTester) DualStackCompatible(networkName string) bool {
dualStackFeatureEnabled := utilfeature.DefaultFeatureGate.Enabled(kubefeatures.IPv6DualStack)
if !dualStackFeatureEnabled {
return false
}
// First tag of hcsshim that has a proper check for dual stack support is v0.8.22 due to a bug.
if err := hcn.IPv6DualStackSupported(); err != nil {
// Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing

View File

@@ -104,23 +104,6 @@ func dropDisabledFields(node *api.Node, oldNode *api.Node) {
node.Spec.ConfigSource = nil
}
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && !multiNodeCIDRsInUse(oldNode) {
if len(node.Spec.PodCIDRs) > 1 {
node.Spec.PodCIDRs = node.Spec.PodCIDRs[0:1]
}
}
}
// multiNodeCIDRsInUse returns true if Node.Spec.PodCIDRs is greater than one
func multiNodeCIDRsInUse(node *api.Node) bool {
if node == nil {
return false
}
if len(node.Spec.PodCIDRs) > 1 {
return true
}
return false
}
// nodeConfigSourceInUse returns true if node's Spec ConfigSource is set(used)

View File

@@ -92,78 +92,42 @@ func TestDropFields(t *testing.T) {
node *api.Node
oldNode *api.Node
compareNode *api.Node
enableDualStack bool
enableNodeDynamicConfig bool
}{
{
name: "nil pod cidrs",
enableDualStack: false,
node: makeNode(nil, false, false),
oldNode: nil,
compareNode: makeNode(nil, false, false),
name: "nil pod cidrs",
node: makeNode(nil, false, false),
oldNode: nil,
compareNode: makeNode(nil, false, false),
},
{
name: "empty pod ips",
enableDualStack: false,
node: makeNode([]string{}, false, false),
oldNode: nil,
compareNode: makeNode([]string{}, false, false),
name: "empty pod ips",
node: makeNode([]string{}, false, false),
oldNode: nil,
compareNode: makeNode([]string{}, false, false),
},
{
name: "single family ipv6",
enableDualStack: false,
node: makeNode([]string{"2000::/10"}, false, false),
compareNode: makeNode([]string{"2000::/10"}, false, false),
name: "single family ipv6",
node: makeNode([]string{"2000::/10"}, false, false),
compareNode: makeNode([]string{"2000::/10"}, false, false),
},
{
name: "single family ipv4",
enableDualStack: false,
node: makeNode([]string{"10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"10.0.0.0/8"}, false, false),
name: "single family ipv4",
node: makeNode([]string{"10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"10.0.0.0/8"}, false, false),
},
{
name: "dualstack 4-6",
enableDualStack: true,
node: makeNode([]string{"10.0.0.0/8", "2000::/10"}, false, false),
compareNode: makeNode([]string{"10.0.0.0/8", "2000::/10"}, false, false),
name: "dualstack 4-6",
node: makeNode([]string{"10.0.0.0/8", "2000::/10"}, false, false),
compareNode: makeNode([]string{"10.0.0.0/8", "2000::/10"}, false, false),
},
{
name: "dualstack 6-4",
enableDualStack: true,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
},
{
name: "not dualstack 6-4=>4only",
enableDualStack: false,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
oldNode: nil,
compareNode: makeNode([]string{"2000::/10"}, false, false),
},
{
name: "not dualstack 6-4=>as is (used in old)",
enableDualStack: false,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
oldNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
},
{
name: "not dualstack 6-4=>6only",
enableDualStack: false,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
oldNode: nil,
compareNode: makeNode([]string{"2000::/10"}, false, false),
},
{
name: "not dualstack 6-4=>as is (used in old)",
enableDualStack: false,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
oldNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
name: "dualstack 6-4",
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
},
{
name: "new with no Spec.ConfigSource and no Status.Config , enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false,
node: makeNode(nil, false, false),
oldNode: nil,
@@ -171,7 +135,6 @@ func TestDropFields(t *testing.T) {
},
{
name: "new with Spec.ConfigSource and no Status.Config, enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false,
node: makeNode(nil, true, false),
oldNode: nil,
@@ -179,7 +142,6 @@ func TestDropFields(t *testing.T) {
},
{
name: "new with Spec.ConfigSource and Status.Config, enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false,
node: makeNode(nil, true, true),
oldNode: nil,
@@ -187,7 +149,6 @@ func TestDropFields(t *testing.T) {
},
{
name: "update with Spec.ConfigSource and Status.Config (old has none), enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false,
node: makeNode(nil, true, true),
oldNode: makeNode(nil, false, false),
@@ -195,7 +156,6 @@ func TestDropFields(t *testing.T) {
},
{
name: "update with Spec.ConfigSource and Status.Config (old has them), enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false,
node: makeNode(nil, true, true),
oldNode: makeNode(nil, true, true),
@@ -203,7 +163,6 @@ func TestDropFields(t *testing.T) {
},
{
name: "update with Spec.ConfigSource and Status.Config (old has Status.Config), enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false,
node: makeNode(nil, true, true),
oldNode: makeNode(nil, false, true),
@@ -211,7 +170,6 @@ func TestDropFields(t *testing.T) {
},
{
name: "new with Spec.ConfigSource and Status.Config, enableNodeDynamicConfig enabled",
enableDualStack: false,
enableNodeDynamicConfig: true,
node: makeNode(nil, true, true),
oldNode: nil,
@@ -221,7 +179,6 @@ func TestDropFields(t *testing.T) {
for _, tc := range testCases {
func() {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicKubeletConfig, tc.enableNodeDynamicConfig)()
dropDisabledFields(tc.node, tc.oldNode)

View File

@@ -215,7 +215,7 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
// allocator for secondary service ip range
var secondaryServiceClusterIPAllocator ipallocator.Interface
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && c.SecondaryServiceIPRange.IP != nil {
if c.SecondaryServiceIPRange.IP != nil {
var secondaryServiceClusterIPRegistry rangeallocation.RangeRegistry
secondaryServiceClusterIPAllocator, err = ipallocator.New(&c.SecondaryServiceIPRange, func(max int, rangeSpec string) (allocator.Interface, error) {
mem := allocator.NewAllocationMap(max, rangeSpec)

View File

@@ -29,10 +29,6 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
netutils "k8s.io/utils/net"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
)
type mockRangeRegistry struct {
@@ -328,8 +324,6 @@ func TestShouldWorkOnSecondary(t *testing.T) {
}
func TestRepairDualStack(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
fakeClient := fake.NewSimpleClientset()
ipregistry := &mockRangeRegistry{
item: &api.RangeAllocation{Range: "192.168.1.0/24"},
@@ -368,8 +362,6 @@ func TestRepairDualStack(t *testing.T) {
}
func TestRepairLeakDualStack(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
_, cidr, _ := netutils.ParseCIDRSloppy("192.168.1.0/24")
previous, err := ipallocator.NewInMemory(cidr)
if err != nil {
@@ -466,7 +458,6 @@ func TestRepairWithExistingDualStack(t *testing.T) {
// we can saftly create tests that has ipFamilyPolicy:nil
// this will work every where except alloc & validation
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
_, cidr, _ := netutils.ParseCIDRSloppy("192.168.1.0/24")
previous, err := ipallocator.NewInMemory(cidr)
if err != nil {

View File

@@ -107,12 +107,6 @@ func (al *Allocators) initIPFamilyFields(after After, before Before) error {
return nil
}
// gate off. We don't need to validate or default new fields
// we totally depend on existing validation in apis/validation
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil
}
// We don't want to auto-upgrade (add an IP) or downgrade (remove an IP)
// PreferDualStack services following a cluster change to/from
// dual-stackness.
@@ -343,10 +337,6 @@ func (al *Allocators) allocClusterIPs(service *api.Service, dryRun bool) (map[ap
return nil, nil
}
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return al.allocClusterIP(service, dryRun)
}
toAlloc := make(map[api.IPFamily]string)
// at this stage, the only fact we know is that service has correct ip families
// assigned to it. It may have partial assigned ClusterIPs (Upgrade to dual stack)
@@ -391,25 +381,6 @@ func (al *Allocators) allocClusterIPs(service *api.Service, dryRun bool) (map[ap
return allocated, err
}
// standard allocator for dualstackgate==Off, hard wired dependency
// and ignores policy, families and clusterIPs
func (al *Allocators) allocClusterIP(service *api.Service, dryRun bool) (map[api.IPFamily]string, error) {
toAlloc := make(map[api.IPFamily]string)
// get clusterIP.. empty string if user did not specify an ip
toAlloc[al.defaultServiceIPFamily] = service.Spec.ClusterIP
// alloc
allocated, err := al.allocIPs(service, toAlloc, dryRun)
// set
if err == nil {
service.Spec.ClusterIP = allocated[al.defaultServiceIPFamily]
service.Spec.ClusterIPs = []string{allocated[al.defaultServiceIPFamily]}
}
return allocated, err
}
func (al *Allocators) allocIPs(service *api.Service, toAlloc map[api.IPFamily]string, dryRun bool) (map[api.IPFamily]string, error) {
allocated := make(map[api.IPFamily]string)
@@ -687,24 +658,12 @@ func (al *Allocators) updateClusterIPs(after After, before Before, dryRun bool)
// Update service from non-ExternalName to ExternalName, should release ClusterIP if exists.
if oldService.Spec.Type != api.ServiceTypeExternalName && service.Spec.Type == api.ServiceTypeExternalName {
toRelease = make(map[api.IPFamily]string)
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
// for non dual stack enabled cluster we use clusterIPs
toRelease[al.defaultServiceIPFamily] = oldService.Spec.ClusterIP
} else {
// dual stack is enabled, collect ClusterIPs by families
for i, family := range oldService.Spec.IPFamilies {
toRelease[family] = oldService.Spec.ClusterIPs[i]
}
for i, family := range oldService.Spec.IPFamilies {
toRelease[family] = oldService.Spec.ClusterIPs[i]
}
return nil, toRelease, nil
}
// upgrade and downgrade are specific to dualstack
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, nil, nil
}
upgraded := len(oldService.Spec.IPFamilies) == 1 && len(service.Spec.IPFamilies) == 2
downgraded := len(oldService.Spec.IPFamilies) == 2 && len(service.Spec.IPFamilies) == 1
@@ -909,10 +868,6 @@ func (al *Allocators) releaseClusterIPs(service *api.Service) (released map[api.
return nil, nil
}
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return al.releaseClusterIP(service)
}
toRelease := make(map[api.IPFamily]string)
for _, ip := range service.Spec.ClusterIPs {
if netutils.IsIPv6String(ip) {
@@ -924,21 +879,6 @@ func (al *Allocators) releaseClusterIPs(service *api.Service) (released map[api.
return al.releaseIPs(toRelease)
}
// for pre dual stack (gate == off). Hardwired to ClusterIP and ignores all new fields
func (al *Allocators) releaseClusterIP(service *api.Service) (released map[api.IPFamily]string, err error) {
toRelease := make(map[api.IPFamily]string)
// we need to do that to handle cases where allocator is no longer configured on
// cluster
if netutils.IsIPv6String(service.Spec.ClusterIP) {
toRelease[api.IPv6Protocol] = service.Spec.ClusterIP
} else {
toRelease[api.IPv4Protocol] = service.Spec.ClusterIP
}
return al.releaseIPs(toRelease)
}
// This is O(N), but we expect haystack to be small;
// so small that we expect a linear search to be faster
func containsNumber(haystack []int, needle int) bool {

View File

@@ -36,10 +36,8 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
@@ -242,11 +240,6 @@ func (r *REST) defaultOnReadService(service *api.Service) {
// We still want to present a consistent view of them.
normalizeClusterIPs(After{service}, Before{nil})
// The rest of this does not apply unless dual-stack is enabled.
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return
}
// Set ipFamilies and ipFamilyPolicy if needed.
r.defaultOnReadIPFamilies(service)
}

View File

@@ -639,8 +639,6 @@ func TestServiceDefaultOnRead(t *testing.T) {
input: &api.Pod{},
}}
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol})
@@ -727,9 +725,6 @@ func helpTestCreateUpdateDeleteWithFamilies(t *testing.T, testCases []cudTestCas
// NOTE: do not call t.Helper() here. It's more useful for errors to be
// attributed to lines in this function than the caller of it.
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
storage, _, server := newStorage(t, ipFamilies)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
@@ -850,20 +845,18 @@ func verifyEquiv(t testingTInterface, call string, tc *svcTestCase, got *api.Ser
if want.Spec.ClusterIP == "" {
want.Spec.ClusterIP = got.Spec.ClusterIP
}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
if want.Spec.IPFamilyPolicy == nil {
want.Spec.IPFamilyPolicy = got.Spec.IPFamilyPolicy
}
if tc.expectStackDowngrade && len(want.Spec.ClusterIPs) > len(got.Spec.ClusterIPs) {
want.Spec.ClusterIPs = want.Spec.ClusterIPs[0:1]
} else if len(got.Spec.ClusterIPs) > len(want.Spec.ClusterIPs) {
want.Spec.ClusterIPs = append(want.Spec.ClusterIPs, got.Spec.ClusterIPs[len(want.Spec.ClusterIPs):]...)
}
if tc.expectStackDowngrade && len(want.Spec.IPFamilies) > len(got.Spec.ClusterIPs) {
want.Spec.IPFamilies = want.Spec.IPFamilies[0:1]
} else if len(got.Spec.IPFamilies) > len(want.Spec.IPFamilies) {
want.Spec.IPFamilies = append(want.Spec.IPFamilies, got.Spec.IPFamilies[len(want.Spec.IPFamilies):]...)
}
if want.Spec.IPFamilyPolicy == nil {
want.Spec.IPFamilyPolicy = got.Spec.IPFamilyPolicy
}
if tc.expectStackDowngrade && len(want.Spec.ClusterIPs) > len(got.Spec.ClusterIPs) {
want.Spec.ClusterIPs = want.Spec.ClusterIPs[0:1]
} else if len(got.Spec.ClusterIPs) > len(want.Spec.ClusterIPs) {
want.Spec.ClusterIPs = append(want.Spec.ClusterIPs, got.Spec.ClusterIPs[len(want.Spec.ClusterIPs):]...)
}
if tc.expectStackDowngrade && len(want.Spec.IPFamilies) > len(got.Spec.ClusterIPs) {
want.Spec.IPFamilies = want.Spec.IPFamilies[0:1]
} else if len(got.Spec.IPFamilies) > len(want.Spec.IPFamilies) {
want.Spec.IPFamilies = append(want.Spec.IPFamilies, got.Spec.IPFamilies[len(want.Spec.IPFamilies):]...)
}
}
@@ -1021,8 +1014,6 @@ func TestVerifyEquiv(t *testing.T) {
expect: false,
}}
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := verifyEquiv(fakeTestingT{t}, "test", &tc.input, tc.output)
@@ -1098,22 +1089,14 @@ func proveClusterIPsAllocated(t *testing.T, storage *wrapperRESTForTests, before
t.Errorf("%s: expected clusterIP == clusterIPs[0]: %q != %q", callName(before, after), sing, plur)
}
clips := []string{}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
clips = after.Spec.ClusterIPs
} else {
clips = append(clips, after.Spec.ClusterIP)
}
for _, clip := range clips {
for _, clip := range after.Spec.ClusterIPs {
if !ipIsAllocated(t, storage.alloc.serviceIPAllocatorsByFamily[familyOf(clip)], clip) {
t.Errorf("%s: expected clusterIP to be allocated: %q", callName(before, after), clip)
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
if lc, lf := len(after.Spec.ClusterIPs), len(after.Spec.IPFamilies); lc != lf {
t.Errorf("%s: expected same number of clusterIPs and ipFamilies: %d != %d", callName(before, after), lc, lf)
}
if lc, lf := len(after.Spec.ClusterIPs), len(after.Spec.IPFamilies); lc != lf {
t.Errorf("%s: expected same number of clusterIPs and ipFamilies: %d != %d", callName(before, after), lc, lf)
}
for i, fam := range after.Spec.IPFamilies {
@@ -1122,23 +1105,21 @@ func proveClusterIPsAllocated(t *testing.T, storage *wrapperRESTForTests, before
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
if after.Spec.IPFamilyPolicy == nil {
t.Errorf("%s: expected ipFamilyPolicy to be set", callName(before, after))
} else {
pol := *after.Spec.IPFamilyPolicy
fams := len(after.Spec.IPFamilies)
clus := 1
if storage.secondaryIPFamily != "" {
clus = 2
}
if pol == api.IPFamilyPolicySingleStack && fams != 1 {
t.Errorf("%s: expected 1 ipFamily, got %d", callName(before, after), fams)
} else if pol == api.IPFamilyPolicyRequireDualStack && fams != 2 {
t.Errorf("%s: expected 2 ipFamilies, got %d", callName(before, after), fams)
} else if pol == api.IPFamilyPolicyPreferDualStack && fams != clus {
t.Errorf("%s: expected %d ipFamilies, got %d", callName(before, after), clus, fams)
}
if after.Spec.IPFamilyPolicy == nil {
t.Errorf("%s: expected ipFamilyPolicy to be set", callName(before, after))
} else {
pol := *after.Spec.IPFamilyPolicy
fams := len(after.Spec.IPFamilies)
clus := 1
if storage.secondaryIPFamily != "" {
clus = 2
}
if pol == api.IPFamilyPolicySingleStack && fams != 1 {
t.Errorf("%s: expected 1 ipFamily, got %d", callName(before, after), fams)
} else if pol == api.IPFamilyPolicyRequireDualStack && fams != 2 {
t.Errorf("%s: expected 2 ipFamilies, got %d", callName(before, after), fams)
} else if pol == api.IPFamilyPolicyPreferDualStack && fams != clus {
t.Errorf("%s: expected %d ipFamilies, got %d", callName(before, after), clus, fams)
}
}
@@ -1180,13 +1161,7 @@ func proveClusterIPsDeallocated(t *testing.T, storage *wrapperRESTForTests, befo
}
if before != nil && before.Spec.ClusterIP != api.ClusterIPNone {
clips := []string{}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
clips = before.Spec.ClusterIPs
} else {
clips = append(clips, before.Spec.ClusterIP)
}
for _, clip := range clips {
for _, clip := range before.Spec.ClusterIPs {
if ipIsAllocated(t, storage.alloc.serviceIPAllocatorsByFamily[familyOf(clip)], clip) {
t.Errorf("%s: expected clusterIP to be deallocated: %q", callName(before, after), clip)
}
@@ -1289,35 +1264,10 @@ func TestCreateIgnoresIPsForExternalName(t *testing.T) {
testCases := []struct {
name string
clusterFamilies []api.IPFamily
enableDualStack bool
cases []testCase
}{{
name: "singlestack:v4_gate:off",
clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
cases: []testCase{{
name: "Policy:unset_Families:unset",
svc: svctest.MakeService("foo"),
}, {
name: "Policy:SingleStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "Policy:PreferDualStack_Families:v4v6",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol)),
}, {
name: "Policy:RequireDualStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}},
}, {
name: "singlestack:v6_gate:on",
name: "singlestack:v6",
clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
cases: []testCase{{
name: "Policy:unset_Families:unset",
svc: svctest.MakeService("foo"),
@@ -1341,32 +1291,8 @@ func TestCreateIgnoresIPsForExternalName(t *testing.T) {
expectError: true,
}},
}, {
name: "dualstack:v4v6_gate:off",
clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
enableDualStack: false,
cases: []testCase{{
name: "Policy:unset_Families:unset",
svc: svctest.MakeService("foo"),
}, {
name: "Policy:SingleStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "Policy:PreferDualStack_Families:v4v6",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol)),
}, {
name: "Policy:RequireDualStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}},
}, {
name: "dualstack:v6v4_gate:on",
name: "dualstack:v6v4",
clusterFamilies: []api.IPFamily{api.IPv6Protocol, api.IPv4Protocol},
enableDualStack: true,
cases: []testCase{{
name: "Policy:unset_Families:unset",
svc: svctest.MakeService("foo"),
@@ -1393,8 +1319,6 @@ func TestCreateIgnoresIPsForExternalName(t *testing.T) {
for _, otc := range testCases {
t.Run(otc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, otc.enableDualStack)()
storage, _, server := newStorage(t, otc.clusterFamilies)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
@@ -1437,159 +1361,6 @@ func TestCreateIgnoresIPsForExternalName(t *testing.T) {
}
}
// Prove that create ignores IPFamily stuff when dual-stack is disabled.
func TestCreateIgnoresIPFamilyWithoutDualStack(t *testing.T) {
// These cases were chosen from the full gamut to ensure all "interesting"
// cases are covered.
testCases := []struct {
name string
svc *api.Service
}{
//----------------------------------------
// ClusterIP:unset
//----------------------------------------
{
name: "ClusterIP:unset_Policy:unset_Families:unset",
svc: svctest.MakeService("foo"),
}, {
name: "ClusterIP:unset_Policy:unset_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:unset_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:SingleStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack)),
}, {
name: "ClusterIP:unset_Policy:SingleStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:SingleStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:PreferDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack)),
}, {
name: "ClusterIP:unset_Policy:PreferDualStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:PreferDualStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:RequireDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
}, {
name: "ClusterIP:unset_Policy:RequireDualStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:RequireDualStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
},
//----------------------------------------
// ClusterIPs:v4v6
//----------------------------------------
{
name: "ClusterIPs:v4v6_Policy:unset_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetClusterIPs("10.0.0.1", "2000::1")),
}, {
name: "ClusterIPs:v4v6_Policy:unset_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetClusterIPs("10.0.0.1", "2000::1"),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIPs:v4v6_Policy:RequireDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetClusterIPs("10.0.0.1", "2000::1"),
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
},
//----------------------------------------
// Headless
//----------------------------------------
{
name: "Headless_Policy:unset_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetHeadless),
}, {
name: "Headless_Policy:unset_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "Headless_Policy:RequireDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
},
//----------------------------------------
// HeadlessSelectorless
//----------------------------------------
{
name: "HeadlessSelectorless_Policy:unset_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetSelector(nil)),
}, {
name: "HeadlessSelectorless_Policy:unset_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetSelector(nil),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "HeadlessSelectorless_Policy:RequireDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetSelector(nil),
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
},
}
// This test is ONLY with the gate off.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, false)()
// Do this in the outer scope for performance.
storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol})
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
createdObj, err := storage.Create(ctx, tc.svc, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Fatalf("unexpected error creating service: %v", err)
}
defer storage.Delete(ctx, tc.svc.Name, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{})
createdSvc := createdObj.(*api.Service)
// The gate is off - these should always be empty.
if want, got := fmtIPFamilyPolicy(nil), fmtIPFamilyPolicy(createdSvc.Spec.IPFamilyPolicy); want != got {
t.Errorf("wrong IPFamilyPolicy: want %s, got %s", want, got)
}
if want, got := fmtIPFamilies(nil), fmtIPFamilies(createdSvc.Spec.IPFamilies); want != got {
t.Errorf("wrong IPFamilies: want %s, got %s", want, got)
}
})
}
}
// Prove that create initializes clusterIPs from clusterIP. This simplifies
// later tests to not need to re-prove this.
func TestCreateInitClusterIPsFromClusterIP(t *testing.T) {
@@ -1635,9 +1406,6 @@ func TestCreateInitClusterIPsFromClusterIP(t *testing.T) {
svctest.SetClusterIP("2000::1")),
}}
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
storage, _, server := newStorage(t, tc.clusterFamilies)
@@ -6093,9 +5861,6 @@ func TestCreateInitIPFields(t *testing.T) {
},
}
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, otc := range testCases {
t.Run(otc.name, func(t *testing.T) {
@@ -6250,8 +6015,6 @@ func TestCreateInvalidClusterIPInputs(t *testing.T) {
expect: []string{"must be a valid IP"},
}}
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
storage, _, server := newStorage(t, tc.families)
@@ -6291,9 +6054,6 @@ func TestCreateDeleteReuse(t *testing.T) {
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol)),
}}
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol})
@@ -6729,47 +6489,38 @@ func TestCreateSkipsAllocationsForHeadless(t *testing.T) {
testCases := []struct {
name string
clusterFamilies []api.IPFamily
enableDualStack bool
svc *api.Service
expectError bool
}{{
name: "singlestack:v4_gate:off",
name: "singlestack:v4",
clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo"),
}, {
name: "singlestack:v6_gate:on",
name: "singlestack:v6",
clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo"),
}, {
name: "dualstack:v4v6_gate:off",
name: "dualstack:v4v6",
clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo"),
}, {
name: "dualstack:v6v4_gate:on",
name: "dualstack:v6v4",
clusterFamilies: []api.IPFamily{api.IPv6Protocol, api.IPv4Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo"),
}, {
name: "singlestack:v4_gate:off_type:NodePort",
name: "singlestack:v4_type:NodePort",
clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo", svctest.SetTypeNodePort),
expectError: true,
}, {
name: "singlestack:v6_gate:on_type:LoadBalancer",
name: "singlestack:v6_type:LoadBalancer",
clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetTypeLoadBalancer),
expectError: true,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
storage, _, server := newStorage(t, tc.clusterFamilies)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
@@ -6805,54 +6556,43 @@ func TestCreateDryRun(t *testing.T) {
testCases := []struct {
name string
clusterFamilies []api.IPFamily
enableDualStack bool
svc *api.Service
}{{
name: "singlestack:v4_gate:off_clusterip:unset",
name: "singlestack:v4_clusterip:unset",
clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo"),
}, {
name: "singlestack:v4_gate:off_clusterip:set",
name: "singlestack:v4_clusterip:set",
clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo", svctest.SetClusterIPs("10.0.0.1")),
}, {
name: "singlestack:v6_gate:on_clusterip:unset",
name: "singlestack:v6_clusterip:unset",
clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo"),
}, {
name: "singlestack:v6_gate:on_clusterip:set",
name: "singlestack:v6_clusterip:set",
clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetClusterIPs("2000::1")),
}, {
name: "dualstack:v4v6_gate:on_clusterip:unset",
name: "dualstack:v4v6_clusterip:unset",
clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack)),
}, {
name: "dualstack:v4v6_gate:on_clusterip:set",
name: "dualstack:v4v6_clusterip:set",
clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack), svctest.SetClusterIPs("10.0.0.1", "2000::1")),
}, {
name: "singlestack:v4_gate:off_type:NodePort_nodeport:unset",
name: "singlestack:v4_type:NodePort_nodeport:unset",
clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo", svctest.SetTypeNodePort),
}, {
name: "singlestack:v4_gate:on_type:LoadBalancer_nodePort:set",
name: "singlestack:v4_type:LoadBalancer_nodePort:set",
clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetTypeLoadBalancer, svctest.SetUniqueNodePorts),
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
storage, _, server := newStorage(t, tc.clusterFamilies)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
@@ -6886,9 +6626,6 @@ func TestCreateDryRun(t *testing.T) {
func TestDeleteWithFinalizer(t *testing.T) {
svcName := "foo"
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol})
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
@@ -6962,33 +6699,29 @@ func TestDeleteWithFinalizer(t *testing.T) {
// Prove that a dry-run delete doesn't actually deallocate IPs or ports.
func TestDeleteDryRun(t *testing.T) {
testCases := []struct {
name string
enableDualStack bool
svc *api.Service
}{{
name: "gate:off",
enableDualStack: false,
svc: svctest.MakeService("foo",
svctest.SetTypeLoadBalancer,
svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal)),
}, {
name: "gate:on",
enableDualStack: true,
svc: svctest.MakeService("foo",
svctest.SetTypeLoadBalancer,
svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal)),
}}
name string
svc *api.Service
}{
{
name: "v4",
svc: svctest.MakeService("foo",
svctest.SetTypeLoadBalancer,
svctest.SetIPFamilies(api.IPv4Protocol),
svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal)),
},
{
name: "v4v6",
svc: svctest.MakeService("foo",
svctest.SetTypeLoadBalancer,
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol),
svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal)),
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
families := []api.IPFamily{api.IPv4Protocol}
if tc.enableDualStack {
families = append(families, api.IPv6Protocol)
}
storage, _, server := newStorage(t, families)
storage, _, server := newStorage(t, tc.svc.Spec.IPFamilies)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()

View File

@@ -162,14 +162,6 @@ func (svcStrategy) AllowUnconditionalUpdate() bool {
// newSvc.Spec.MyFeature = nil
// }
func dropServiceDisabledFields(newSvc *api.Service, oldSvc *api.Service) {
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && !serviceDualStackFieldsInUse(oldSvc) {
newSvc.Spec.IPFamilies = nil
newSvc.Spec.IPFamilyPolicy = nil
if len(newSvc.Spec.ClusterIPs) > 1 {
newSvc.Spec.ClusterIPs = newSvc.Spec.ClusterIPs[0:1]
}
}
// Clear AllocateLoadBalancerNodePorts if ServiceLBNodePortControl is not enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceLBNodePortControl) {
if !allocateLoadBalancerNodePortsInUse(oldSvc) {
@@ -211,19 +203,6 @@ func allocateLoadBalancerNodePortsInUse(svc *api.Service) bool {
return svc.Spec.AllocateLoadBalancerNodePorts != nil
}
// returns true if svc.Spec.ServiceIPFamily field is in use
func serviceDualStackFieldsInUse(svc *api.Service) bool {
if svc == nil {
return false
}
ipFamilyPolicyInUse := svc.Spec.IPFamilyPolicy != nil
ipFamiliesInUse := len(svc.Spec.IPFamilies) > 0
ClusterIPsInUse := len(svc.Spec.ClusterIPs) > 1
return ipFamilyPolicyInUse || ipFamiliesInUse || ClusterIPsInUse
}
// returns true when the svc.Status.Conditions field is in use.
func serviceConditionsInUse(svc *api.Service) bool {
if svc == nil {

View File

@@ -144,15 +144,6 @@ func TestServiceStatusStrategy(t *testing.T) {
}
}
func makeServiceWithIPFamilies(ipfamilies []api.IPFamily, ipFamilyPolicy *api.IPFamilyPolicyType) *api.Service {
return &api.Service{
Spec: api.ServiceSpec{
IPFamilies: ipfamilies,
IPFamilyPolicy: ipFamilyPolicy,
},
}
}
func makeServiceWithConditions(conditions []metav1.Condition) *api.Service {
return &api.Service{
Status: api.ServiceStatus{
@@ -192,15 +183,10 @@ func makeServiceWithInternalTrafficPolicy(policy *api.ServiceInternalTrafficPoli
}
func TestDropDisabledField(t *testing.T) {
requireDualStack := api.IPFamilyPolicyRequireDualStack
preferDualStack := api.IPFamilyPolicyPreferDualStack
singleStack := api.IPFamilyPolicySingleStack
localInternalTrafficPolicy := api.ServiceInternalTrafficPolicyLocal
testCases := []struct {
name string
enableDualStack bool
enableMixedProtocol bool
enableLoadBalancerClass bool
enableInternalTrafficPolicy bool
@@ -208,64 +194,6 @@ func TestDropDisabledField(t *testing.T) {
oldSvc *api.Service
compareSvc *api.Service
}{
{
name: "not dual stack, field not used",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, nil),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "not dual stack, field used in old and new",
enableDualStack: false,
svc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv4Protocol}, nil),
oldSvc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv4Protocol}, nil),
compareSvc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv4Protocol}, nil),
},
{
name: "dualstack, field used",
enableDualStack: true,
svc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv6Protocol}, nil),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv6Protocol}, nil),
},
/* preferDualStack field */
{
name: "not dual stack, fields is not use",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, nil),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "not dual stack, fields used in new, not in old",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, &preferDualStack),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "not dual stack, fields used in new, not in old",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, &requireDualStack),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "not dual stack, fields not used in old (single stack)",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, nil),
oldSvc: makeServiceWithIPFamilies(nil, &singleStack),
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "dualstack, field used",
enableDualStack: true,
svc: makeServiceWithIPFamilies(nil, &singleStack),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, &singleStack),
},
/* svc.Status.Conditions */
{
name: "mixed protocol not enabled, field not used in old, not used in new",
@@ -463,7 +391,6 @@ func TestDropDisabledField(t *testing.T) {
}
for _, tc := range testCases {
func() {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MixedProtocolLBService, tc.enableMixedProtocol)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceLoadBalancerClass, tc.enableLoadBalancerClass)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceInternalTrafficPolicy, tc.enableInternalTrafficPolicy)()

View File

@@ -89,6 +89,9 @@ type Interface interface {
// mapped to the same IP:PORT and consequently some suffer packet
// drops.
HasRandomFully() bool
// Present checks if the kernel supports the iptable interface
Present() bool
}
// Protocol defines the ip protocol either ipv4 or ipv6
@@ -723,6 +726,16 @@ func (runner *runner) HasRandomFully() bool {
return runner.hasRandomFully
}
// Present tests if iptable is supported on current kernel by checking the existence
// of default table and chain
func (runner *runner) Present() bool {
if _, err := runner.ChainExists(TableNAT, ChainPostrouting); err != nil {
return false
}
return true
}
var iptablesNotFoundStrings = []string{
// iptables-legacy [-A|-I] BAD-CHAIN [...]
// iptables-legacy [-C|-D] GOOD-CHAIN [...non-matching rule...]

View File

@@ -176,4 +176,8 @@ func (f *FakeIPTables) HasRandomFully() bool {
return f.hasRandomFully
}
func (f *FakeIPTables) Present() bool {
return true
}
var _ = iptables.Interface(&FakeIPTables{})

View File

@@ -33,10 +33,8 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net"
)
@@ -122,12 +120,10 @@ func GetNodeHostIPs(node *v1.Node) ([]net.IP, error) {
}
nodeIPs := []net.IP{allIPs[0]}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
for _, ip := range allIPs {
if netutils.IsIPv6(ip) != netutils.IsIPv6(nodeIPs[0]) {
nodeIPs = append(nodeIPs, ip)
break
}
for _, ip := range allIPs {
if netutils.IsIPv6(ip) != netutils.IsIPv6(nodeIPs[0]) {
nodeIPs = append(nodeIPs, ip)
break
}
}

View File

@@ -23,9 +23,6 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net"
)
@@ -99,7 +96,6 @@ func TestGetNodeHostIPs(t *testing.T) {
testcases := []struct {
name string
addresses []v1.NodeAddress
dualStack bool
expectIPs []net.IP
}{
@@ -141,7 +137,7 @@ func TestGetNodeHostIPs(t *testing.T) {
expectIPs: []net.IP{netutils.ParseIPSloppy("4.3.2.1")},
},
{
name: "dual-stack node, single-stack cluster",
name: "dual-stack node",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
@@ -149,52 +145,50 @@ func TestGetNodeHostIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4")},
},
{
name: "dual-stack node, dual-stack cluster",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
dualStack: true,
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")},
},
{
name: "dual-stack node, different order, single-stack cluster",
name: "dual-stack node",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4")},
},
{
name: "dual-stack node, different order, dual-stack cluster",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
dualStack: true,
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")},
},
{
name: "dual-stack node, IPv6-first, no internal IPv4, single-stack cluster",
name: "dual-stack node, different order",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")},
},
{
name: "dual-stack node, different order",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")},
},
{
name: "dual-stack node, IPv6-first, no internal IPv4",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
},
expectIPs: []net.IP{netutils.ParseIPSloppy("a:b::c:d")},
expectIPs: []net.IP{netutils.ParseIPSloppy("a:b::c:d"), netutils.ParseIPSloppy("4.3.2.1")},
},
{
name: "dual-stack node, IPv6-first, no internal IPv4, dual-stack cluster",
@@ -204,14 +198,12 @@ func TestGetNodeHostIPs(t *testing.T) {
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
},
dualStack: true,
expectIPs: []net.IP{netutils.ParseIPSloppy("a:b::c:d"), netutils.ParseIPSloppy("4.3.2.1")},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.dualStack)()
node := &v1.Node{
Status: v1.NodeStatus{Addresses: tc.addresses},
}