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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 455 additions and 1373 deletions

View File

@ -9317,7 +9317,7 @@
"type": "string"
},
"clusterIPs": {
"description": "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nUnless the \"IPv6DualStack\" feature gate is enabled, this field is limited to one value, which must be the same as the clusterIP field. If the feature gate is enabled, this field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"description": "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nThis field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"items": {
"type": "string"
},
@ -9349,7 +9349,7 @@
"type": "string"
},
"ipFamilies": {
"description": "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service, and is gated by the \"IPv6DualStack\" feature gate. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.",
"description": "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.",
"items": {
"type": "string"
},
@ -9357,7 +9357,7 @@
"x-kubernetes-list-type": "atomic"
},
"ipFamilyPolicy": {
"description": "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service, and is gated by the \"IPv6DualStack\" feature gate. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.",
"description": "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.",
"type": "string"
},
"loadBalancerClass": {

View File

@ -25,13 +25,11 @@ import (
"net"
"strings"
utilfeature "k8s.io/apiserver/pkg/util/feature"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/cloud-provider/app"
cloudcontrollerconfig "k8s.io/cloud-provider/app/config"
genericcontrollermanager "k8s.io/controller-manager/app"
"k8s.io/controller-manager/controller"
"k8s.io/controller-manager/pkg/features"
"k8s.io/klog/v2"
nodeipamcontrolleroptions "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
nodeipamcontroller "k8s.io/kubernetes/pkg/controller/nodeipam"
@ -79,11 +77,6 @@ func startNodeIpamController(initContext app.ControllerInitContext, ccmConfig *c
return nil, false, err
}
// failure: more than one cidr and dual stack is not enabled
if len(clusterCIDRs) > 1 && !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and dualstack feature is not enabled", len(clusterCIDRs))
}
// failure: more than one cidr but they are not configured as dual stack
if len(clusterCIDRs) > 1 && !dualStack {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily", len(clusterCIDRs))
@ -111,11 +104,6 @@ func startNodeIpamController(initContext app.ControllerInitContext, ccmConfig *c
// the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided
if serviceCIDR != nil && secondaryServiceCIDR != nil {
// should have dual stack flag enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("secondary service cidr is provided and IPv6DualStack feature is not enabled")
}
// should be dual stack (from different IPFamilies)
dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR})
if err != nil {
@ -126,24 +114,11 @@ func startNodeIpamController(initContext app.ControllerInitContext, ccmConfig *c
}
}
var nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6 int
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
// only --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 supported with dual stack clusters.
// --node-cidr-mask-size flag is incompatible with dual stack clusters.
nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6, err = setNodeCIDRMaskSizesDualStack(nodeIPAMConfig)
} else {
// only --node-cidr-mask-size supported with single stack clusters.
// --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 flags are incompatible with dual stack clusters.
nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6, err = setNodeCIDRMaskSizes(nodeIPAMConfig)
}
nodeCIDRMaskSizes, err := setNodeCIDRMaskSizes(nodeIPAMConfig, clusterCIDRs)
if err != nil {
return nil, false, err
}
// get list of node cidr mask sizes
nodeCIDRMaskSizes := getNodeCIDRMaskSizes(clusterCIDRs, nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6)
nodeIpamController, err := nodeipamcontroller.NewNodeIpamController(
ctx.InformerFactory.Core().V1().Nodes(),
cloud,
@ -180,50 +155,75 @@ func processCIDRs(cidrsList string) ([]*net.IPNet, bool, error) {
return cidrs, dualstack, nil
}
// setNodeCIDRMaskSizes returns the IPv4 and IPv6 node cidr mask sizes.
// If --node-cidr-mask-size not set, then it will return default IPv4 and IPv6 cidr mask sizes.
func setNodeCIDRMaskSizes(cfg nodeipamconfig.NodeIPAMControllerConfiguration) (int, int, error) {
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
// NodeCIDRMaskSizeIPv4 and NodeCIDRMaskSizeIPv6 can be used only for dual-stack clusters
if cfg.NodeCIDRMaskSizeIPv4 != 0 || cfg.NodeCIDRMaskSizeIPv6 != 0 {
return ipv4Mask, ipv6Mask, errors.New("usage of --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 are not allowed with non dual-stack clusters")
}
if cfg.NodeCIDRMaskSize != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSize)
ipv6Mask = int(cfg.NodeCIDRMaskSize)
}
return ipv4Mask, ipv6Mask, nil
}
// setNodeCIDRMaskSizesDualStack returns the IPv4 and IPv6 node cidr mask sizes to the value provided
// setNodeCIDRMaskSizes returns the IPv4 and IPv6 node cidr mask sizes to the value provided
// for --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 respectively. If value not provided,
// then it will return default IPv4 and IPv6 cidr mask sizes.
func setNodeCIDRMaskSizesDualStack(cfg nodeipamconfig.NodeIPAMControllerConfiguration) (int, int, error) {
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
// NodeCIDRMaskSize can be used only for single stack clusters
if cfg.NodeCIDRMaskSize != 0 {
return ipv4Mask, ipv6Mask, errors.New("usage of --node-cidr-mask-size is not allowed with dual-stack clusters")
func setNodeCIDRMaskSizes(cfg nodeipamconfig.NodeIPAMControllerConfiguration, clusterCIDRs []*net.IPNet) ([]int, error) {
sortedSizes := func(maskSizeIPv4, maskSizeIPv6 int) []int {
nodeMaskCIDRs := make([]int, len(clusterCIDRs))
for idx, clusterCIDR := range clusterCIDRs {
if netutils.IsIPv6CIDR(clusterCIDR) {
nodeMaskCIDRs[idx] = maskSizeIPv6
} else {
nodeMaskCIDRs[idx] = maskSizeIPv4
}
}
return nodeMaskCIDRs
}
if cfg.NodeCIDRMaskSizeIPv4 != 0 {
// --node-cidr-mask-size flag is incompatible with dual stack clusters.
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
isDualstack := len(clusterCIDRs) > 1
// case one: cluster is dualstack (i.e, more than one cidr)
if isDualstack {
// if --node-cidr-mask-size then fail, user must configure the correct dual-stack mask sizes (or use default)
if cfg.NodeCIDRMaskSize != 0 {
return nil, errors.New("usage of --node-cidr-mask-size is not allowed with dual-stack clusters")
}
if cfg.NodeCIDRMaskSizeIPv4 != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
if cfg.NodeCIDRMaskSizeIPv6 != 0 {
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return sortedSizes(ipv4Mask, ipv6Mask), nil
}
maskConfigured := cfg.NodeCIDRMaskSize != 0
maskV4Configured := cfg.NodeCIDRMaskSizeIPv4 != 0
maskV6Configured := cfg.NodeCIDRMaskSizeIPv6 != 0
isSingleStackIPv6 := netutils.IsIPv6CIDR(clusterCIDRs[0])
// original flag is set
if maskConfigured {
// original mask flag is still the main reference.
if maskV4Configured || maskV6Configured {
return nil, errors.New("usage of --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 is not allowed if --node-cidr-mask-size is set. For dual-stack clusters please unset it and use IPFamily specific flags")
}
mask := int(cfg.NodeCIDRMaskSize)
return sortedSizes(mask, mask), nil
}
if maskV4Configured {
if isSingleStackIPv6 {
return nil, errors.New("usage of --node-cidr-mask-size-ipv4 is not allowed for a single-stack IPv6 cluster")
}
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
if cfg.NodeCIDRMaskSizeIPv6 != 0 {
// !maskV4Configured && !maskConfigured && maskV6Configured
if maskV6Configured {
if !isSingleStackIPv6 {
return nil, errors.New("usage of --node-cidr-mask-size-ipv6 is not allowed for a single-stack IPv4 cluster")
}
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return ipv4Mask, ipv6Mask, nil
}
// getNodeCIDRMaskSizes is a helper function that helps the generate the node cidr mask
// sizes slice based on the cluster cidr slice
func getNodeCIDRMaskSizes(clusterCIDRs []*net.IPNet, maskSizeIPv4, maskSizeIPv6 int) []int {
nodeMaskCIDRs := make([]int, len(clusterCIDRs))
for idx, clusterCIDR := range clusterCIDRs {
if netutils.IsIPv6CIDR(clusterCIDR) {
nodeMaskCIDRs[idx] = maskSizeIPv6
} else {
nodeMaskCIDRs[idx] = maskSizeIPv4
}
}
return nodeMaskCIDRs
return sortedSizes(ipv4Mask, ipv6Mask), nil
}

View File

@ -27,7 +27,6 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net"
)
@ -54,13 +53,7 @@ func validateClusterIPFlags(options *ServerRunOptions) []error {
errs = append(errs, err)
}
// Secondary IP validation
// ControllerManager needs DualStack feature flags
secondaryServiceClusterIPRangeUsed := (options.SecondaryServiceClusterIPRange.IP != nil)
if secondaryServiceClusterIPRangeUsed && !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
errs = append(errs, fmt.Errorf("secondary service cluster-ip range(--service-cluster-ip-range[1]) can only be used if %v feature is enabled", string(features.IPv6DualStack)))
}
// note: While the cluster might be dualstack (i.e. pods with multiple IPs), the user may choose
// to only ingress traffic within and into the cluster on one IP family only. this family is decided
// by the range set on --service-cluster-ip-range. If/when the user decides to use dual stack services

View File

@ -20,9 +20,6 @@ import (
"net"
"testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net"
)
@ -55,90 +52,61 @@ func makeOptionsWithCIDRs(serviceCIDR string, secondaryServiceCIDR string) *Serv
func TestClusterServiceIPRange(t *testing.T) {
testCases := []struct {
name string
options *ServerRunOptions
enableDualStack bool
expectErrors bool
name string
options *ServerRunOptions
expectErrors bool
}{
{
name: "no service cidr",
expectErrors: true,
options: makeOptionsWithCIDRs("", ""),
enableDualStack: false,
name: "no service cidr",
expectErrors: true,
options: makeOptionsWithCIDRs("", ""),
},
{
name: "only secondary service cidr, dual stack gate on",
expectErrors: true,
options: makeOptionsWithCIDRs("", "10.0.0.0/16"),
enableDualStack: true,
name: "only secondary service cidr",
expectErrors: true,
options: makeOptionsWithCIDRs("", "10.0.0.0/16"),
},
{
name: "only secondary service cidr, dual stack gate off",
expectErrors: true,
options: makeOptionsWithCIDRs("", "10.0.0.0/16"),
enableDualStack: false,
name: "primary and secondary are provided but not dual stack v4-v4",
expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/16", "11.0.0.0/16"),
},
{
name: "primary and secondary are provided but not dual stack v4-v4",
expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/16", "11.0.0.0/16"),
enableDualStack: true,
name: "primary and secondary are provided but not dual stack v6-v6",
expectErrors: true,
options: makeOptionsWithCIDRs("2000::/108", "3000::/108"),
},
{
name: "primary and secondary are provided but not dual stack v6-v6",
expectErrors: true,
options: makeOptionsWithCIDRs("2000::/108", "3000::/108"),
enableDualStack: true,
name: "service cidr is too big",
expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/8", ""),
},
{
name: "valid dual stack with gate disabled",
expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/108"),
enableDualStack: false,
},
{
name: "service cidr is too big",
expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/8", ""),
enableDualStack: true,
},
{
name: "dual-stack secondary cidr too big",
expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/64"),
enableDualStack: true,
},
{
name: "valid v6-v4 dual stack + gate on + endpointSlice gate is on",
expectErrors: false,
options: makeOptionsWithCIDRs("3000::/108", "10.0.0.0/16"),
enableDualStack: true,
name: "dual-stack secondary cidr too big",
expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/64"),
},
/* success cases */
{
name: "valid primary",
expectErrors: false,
options: makeOptionsWithCIDRs("10.0.0.0/16", ""),
enableDualStack: false,
name: "valid primary",
expectErrors: false,
options: makeOptionsWithCIDRs("10.0.0.0/16", ""),
},
{
name: "valid v4-v6 dual stack + gate on",
expectErrors: false,
options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/108"),
enableDualStack: true,
name: "valid v4-v6 dual stack",
expectErrors: false,
options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/108"),
},
{
name: "valid v6-v4 dual stack + gate on",
expectErrors: false,
options: makeOptionsWithCIDRs("3000::/108", "10.0.0.0/16"),
enableDualStack: true,
name: "valid v6-v4 dual stack",
expectErrors: false,
options: makeOptionsWithCIDRs("3000::/108", "10.0.0.0/16"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
errs := validateClusterIPFlags(tc.options)
if len(errs) > 0 && !tc.expectErrors {
t.Errorf("expected no errors, errors found %+v", errs)

View File

@ -110,11 +110,6 @@ func startNodeIpamController(ctx ControllerContext) (controller.Interface, bool,
return nil, false, err
}
// failure: more than one cidr and dual stack is not enabled
if len(clusterCIDRs) > 1 && !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and dualstack or EndpointSlice feature is not enabled", len(clusterCIDRs))
}
// failure: more than one cidr but they are not configured as dual stack
if len(clusterCIDRs) > 1 && !dualStack {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily)", len(clusterCIDRs))
@ -142,11 +137,6 @@ func startNodeIpamController(ctx ControllerContext) (controller.Interface, bool,
// the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided
if serviceCIDR != nil && secondaryServiceCIDR != nil {
// should have dual stack flag enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("secondary service cidr is provided and IPv6DualStack feature is not enabled")
}
// should be dual stack (from different IPFamilies)
dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR})
if err != nil {
@ -157,24 +147,13 @@ func startNodeIpamController(ctx ControllerContext) (controller.Interface, bool,
}
}
var nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6 int
if dualStack {
// only --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 supported with dual stack clusters.
// --node-cidr-mask-size flag is incompatible with dual stack clusters.
nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6, err = setNodeCIDRMaskSizesDualStack(ctx.ComponentConfig.NodeIPAMController)
} else {
// only --node-cidr-mask-size supported with single stack clusters.
// --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 flags are incompatible with single stack clusters.
nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6, err = setNodeCIDRMaskSizes(ctx.ComponentConfig.NodeIPAMController)
}
// only --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 supported with dual stack clusters.
// --node-cidr-mask-size flag is incompatible with dual stack clusters.
nodeCIDRMaskSizes, err := setNodeCIDRMaskSizes(ctx.ComponentConfig.NodeIPAMController, clusterCIDRs)
if err != nil {
return nil, false, err
}
// get list of node cidr mask sizes
nodeCIDRMaskSizes := getNodeCIDRMaskSizes(clusterCIDRs, nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6)
nodeIpamController, err := nodeipamcontroller.NewNodeIpamController(
ctx.InformerFactory.Core().V1().Nodes(),
ctx.Cloud,
@ -257,11 +236,6 @@ func startRouteController(ctx ControllerContext) (controller.Interface, bool, er
return nil, false, err
}
// failure: more than one cidr and dual stack is not enabled
if len(clusterCIDRs) > 1 && !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and dualstack feature is not enabled", len(clusterCIDRs))
}
// failure: more than one cidr but they are not configured as dual stack
if len(clusterCIDRs) > 1 && !dualStack {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily", len(clusterCIDRs))
@ -627,54 +601,79 @@ func processCIDRs(cidrsList string) ([]*net.IPNet, bool, error) {
return cidrs, dualstack, nil
}
// setNodeCIDRMaskSizes returns the IPv4 and IPv6 node cidr mask sizes.
// If --node-cidr-mask-size not set, then it will return default IPv4 and IPv6 cidr mask sizes.
func setNodeCIDRMaskSizes(cfg nodeipamconfig.NodeIPAMControllerConfiguration) (int, int, error) {
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
// NodeCIDRMaskSizeIPv4 and NodeCIDRMaskSizeIPv6 can be used only for dual-stack clusters
if cfg.NodeCIDRMaskSizeIPv4 != 0 || cfg.NodeCIDRMaskSizeIPv6 != 0 {
return ipv4Mask, ipv6Mask, errors.New("usage of --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 are not allowed with non dual-stack clusters")
}
if cfg.NodeCIDRMaskSize != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSize)
ipv6Mask = int(cfg.NodeCIDRMaskSize)
}
return ipv4Mask, ipv6Mask, nil
}
// setNodeCIDRMaskSizesDualStack returns the IPv4 and IPv6 node cidr mask sizes to the value provided
// setNodeCIDRMaskSizes returns the IPv4 and IPv6 node cidr mask sizes to the value provided
// for --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 respectively. If value not provided,
// then it will return default IPv4 and IPv6 cidr mask sizes.
func setNodeCIDRMaskSizesDualStack(cfg nodeipamconfig.NodeIPAMControllerConfiguration) (int, int, error) {
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
// NodeCIDRMaskSize can be used only for single stack clusters
if cfg.NodeCIDRMaskSize != 0 {
return ipv4Mask, ipv6Mask, errors.New("usage of --node-cidr-mask-size is not allowed with dual-stack clusters")
func setNodeCIDRMaskSizes(cfg nodeipamconfig.NodeIPAMControllerConfiguration, clusterCIDRs []*net.IPNet) ([]int, error) {
sortedSizes := func(maskSizeIPv4, maskSizeIPv6 int) []int {
nodeMaskCIDRs := make([]int, len(clusterCIDRs))
for idx, clusterCIDR := range clusterCIDRs {
if netutils.IsIPv6CIDR(clusterCIDR) {
nodeMaskCIDRs[idx] = maskSizeIPv6
} else {
nodeMaskCIDRs[idx] = maskSizeIPv4
}
}
return nodeMaskCIDRs
}
if cfg.NodeCIDRMaskSizeIPv4 != 0 {
// --node-cidr-mask-size flag is incompatible with dual stack clusters.
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
isDualstack := len(clusterCIDRs) > 1
// case one: cluster is dualstack (i.e, more than one cidr)
if isDualstack {
// if --node-cidr-mask-size then fail, user must configure the correct dual-stack mask sizes (or use default)
if cfg.NodeCIDRMaskSize != 0 {
return nil, errors.New("usage of --node-cidr-mask-size is not allowed with dual-stack clusters")
}
if cfg.NodeCIDRMaskSizeIPv4 != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
if cfg.NodeCIDRMaskSizeIPv6 != 0 {
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return sortedSizes(ipv4Mask, ipv6Mask), nil
}
maskConfigured := cfg.NodeCIDRMaskSize != 0
maskV4Configured := cfg.NodeCIDRMaskSizeIPv4 != 0
maskV6Configured := cfg.NodeCIDRMaskSizeIPv6 != 0
isSingleStackIPv6 := netutils.IsIPv6CIDR(clusterCIDRs[0])
// original flag is set
if maskConfigured {
// original mask flag is still the main reference.
if maskV4Configured || maskV6Configured {
return nil, errors.New("usage of --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 is not allowed if --node-cidr-mask-size is set. For dual-stack clusters please unset it and use IPFamily specific flags")
}
mask := int(cfg.NodeCIDRMaskSize)
return sortedSizes(mask, mask), nil
}
if maskV4Configured {
if isSingleStackIPv6 {
return nil, errors.New("usage of --node-cidr-mask-size-ipv4 is not allowed for a single-stack IPv6 cluster")
}
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
if cfg.NodeCIDRMaskSizeIPv6 != 0 {
// !maskV4Configured && !maskConfigured && maskV6Configured
if maskV6Configured {
if !isSingleStackIPv6 {
return nil, errors.New("usage of --node-cidr-mask-size-ipv6 is not allowed for a single-stack IPv4 cluster")
}
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return ipv4Mask, ipv6Mask, nil
return sortedSizes(ipv4Mask, ipv6Mask), nil
}
// getNodeCIDRMaskSizes is a helper function that helps the generate the node cidr mask
// sizes slice based on the cluster cidr slice
func getNodeCIDRMaskSizes(clusterCIDRs []*net.IPNet, maskSizeIPv4, maskSizeIPv6 int) []int {
nodeMaskCIDRs := make([]int, len(clusterCIDRs))
for idx, clusterCIDR := range clusterCIDRs {
if netutils.IsIPv6CIDR(clusterCIDR) {
nodeMaskCIDRs[idx] = maskSizeIPv6
} else {
nodeMaskCIDRs[idx] = maskSizeIPv4
}
}
return nodeMaskCIDRs
}
func startStorageVersionGCController(ctx ControllerContext) (controller.Interface, bool, error) {
go storageversiongc.NewStorageVersionGC(
ctx.ClientBuilder.ClientOrDie("storage-version-garbage-collector"),

View File

@ -44,13 +44,11 @@ import (
"k8s.io/apimachinery/pkg/types"
utilnet "k8s.io/apimachinery/pkg/util/net"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes"
toolswatch "k8s.io/client-go/tools/watch"
"k8s.io/component-base/configz"
"k8s.io/component-base/metrics"
utilsysctl "k8s.io/component-helpers/node/utils/sysctl"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/proxy"
proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/config"
"k8s.io/kubernetes/pkg/proxy/apis/config/scheme"
@ -183,29 +181,25 @@ func newProxyServer(
iptInterface = utiliptables.New(execer, primaryProtocol)
var ipt [2]utiliptables.Interface
dualStack := utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && proxyMode != proxyModeUserspace
if dualStack {
dualStack := true // While we assume that node supports, we do further checks below
if proxyMode != proxyModeUserspace {
// Create iptables handlers for both families, one is already created
// Always ordered as IPv4, IPv6
if primaryProtocol == utiliptables.ProtocolIPv4 {
ipt[0] = iptInterface
ipt[1] = utiliptables.New(execer, utiliptables.ProtocolIPv6)
// Just because the feature gate is enabled doesn't mean the node
// actually supports dual-stack
if _, err := ipt[1].ChainExists(utiliptables.TableNAT, utiliptables.ChainPostrouting); err != nil {
klog.Warningf("No iptables support for IPv6: %v", err)
dualStack = false
}
} else {
ipt[0] = utiliptables.New(execer, utiliptables.ProtocolIPv4)
ipt[1] = iptInterface
}
}
if dualStack {
klog.V(0).InfoS("kube-proxy running in dual-stack mode", "ipFamily", iptInterface.Protocol())
} else {
klog.V(0).InfoS("kube-proxy running in single-stack mode", "ipFamily", iptInterface.Protocol())
for _, perFamilyIpt := range ipt {
if !perFamilyIpt.Present() {
klog.V(0).InfoS("kube-proxy running in single-stack mode, this ipFamily is not supported", "ipFamily", perFamilyIpt.Protocol())
dualStack = false
}
}
}
if proxyMode == proxyModeIPTables {
@ -216,8 +210,8 @@ func newProxyServer(
}
if dualStack {
klog.V(0).InfoS("kube-proxy running in dual-stack mode", "ipFamily", iptInterface.Protocol())
klog.V(0).InfoS("Creating dualStackProxier for iptables")
// Always ordered to match []ipt
var localDetectors [2]proxyutiliptables.LocalTrafficDetector
localDetectors, err = getDualStackLocalDetectorTuple(detectLocalMode, config, ipt, nodeInfo)
@ -241,7 +235,8 @@ func newProxyServer(
healthzServer,
config.NodePortAddresses,
)
} else { // Create a single-stack proxier.
} else {
// Create a single-stack proxier if and only if the node does not support dual-stack (i.e, no iptables support).
var localDetector proxyutiliptables.LocalTrafficDetector
localDetector, err = getLocalDetector(detectLocalMode, config, iptInterface, nodeInfo)
if err != nil {

View File

@ -39,7 +39,7 @@ const (
// InitFeatureGates are the default feature gates for the init command
var InitFeatureGates = FeatureList{
IPv6DualStack: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Beta}},
IPv6DualStack: {FeatureSpec: featuregate.FeatureSpec{Default: true, LockToDefault: true, PreRelease: featuregate.GA}, HiddenInHelpText: true},
PublicKeysECDSA: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
RootlessControlPlane: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
}

View File

@ -1130,9 +1130,8 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
}
}
}
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && len(nodeIPs) > 1 {
return fmt.Errorf("dual-stack --node-ip %q not supported in a single-stack cluster", kubeServer.NodeIP)
} else if len(nodeIPs) > 2 || (len(nodeIPs) == 2 && netutils.IsIPv6(nodeIPs[0]) == netutils.IsIPv6(nodeIPs[1])) {
if len(nodeIPs) > 2 || (len(nodeIPs) == 2 && netutils.IsIPv6(nodeIPs[0]) == netutils.IsIPv6(nodeIPs[1])) {
return fmt.Errorf("bad --node-ip %q; must contain either a single IP or a dual-stack pair of IPs", kubeServer.NodeIP)
} else if len(nodeIPs) == 2 && kubeServer.CloudProvider != "" {
return fmt.Errorf("dual-stack --node-ip %q not supported when using a cloud provider", kubeServer.NodeIP)

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},
}

View File

@ -4900,12 +4900,9 @@ message ServiceSpec {
// clients must ensure that clusterIPs[0] and clusterIP have the same
// value.
//
// Unless the "IPv6DualStack" feature gate is enabled, this field is
// limited to one value, which must be the same as the clusterIP field. If
// the feature gate is enabled, this field may hold a maximum of two
// entries (dual-stack IPs, in either order). These IPs must correspond to
// the values of the ipFamilies field. Both clusterIPs and ipFamilies are
// governed by the ipFamilyPolicy field.
// This field may hold a maximum of two entries (dual-stack IPs, in either order).
// These IPs must correspond to the values of the ipFamilies field. Both
// clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.
// More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
// +listType=atomic
// +optional
@ -5005,17 +5002,16 @@ message ServiceSpec {
optional SessionAffinityConfig sessionAffinityConfig = 14;
// IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this
// service, and is gated by the "IPv6DualStack" feature gate. This field
// is usually assigned automatically based on cluster configuration and the
// ipFamilyPolicy field. If this field is specified manually, the requested
// family is available in the cluster, and ipFamilyPolicy allows it, it
// will be used; otherwise creation of the service will fail. This field
// is conditionally mutable: it allows for adding or removing a secondary
// IP family, but it does not allow changing the primary IP family of the
// Service. Valid values are "IPv4" and "IPv6". This field only applies
// to Services of types ClusterIP, NodePort, and LoadBalancer, and does
// apply to "headless" services. This field will be wiped when updating a
// Service to type ExternalName.
// service. This field is usually assigned automatically based on cluster
// configuration and the ipFamilyPolicy field. If this field is specified
// manually, the requested family is available in the cluster,
// and ipFamilyPolicy allows it, it will be used; otherwise creation of
// the service will fail. This field is conditionally mutable: it allows
// for adding or removing a secondary IP family, but it does not allow
// changing the primary IP family of the Service. Valid values are "IPv4"
// and "IPv6". This field only applies to Services of types ClusterIP,
// NodePort, and LoadBalancer, and does apply to "headless" services.
// This field will be wiped when updating a Service to type ExternalName.
//
// This field may hold a maximum of two entries (dual-stack families, in
// either order). These families must correspond to the values of the
@ -5026,14 +5022,13 @@ message ServiceSpec {
repeated string ipFamilies = 19;
// IPFamilyPolicy represents the dual-stack-ness requested or required by
// this Service, and is gated by the "IPv6DualStack" feature gate. If
// there is no value provided, then this field will be set to SingleStack.
// Services can be "SingleStack" (a single IP family), "PreferDualStack"
// (two IP families on dual-stack configured clusters or a single IP family
// on single-stack clusters), or "RequireDualStack" (two IP families on
// dual-stack configured clusters, otherwise fail). The ipFamilies and
// clusterIPs fields depend on the value of this field. This field will be
// wiped when updating a service to type ExternalName.
// this Service. If there is no value provided, then this field will be set
// to SingleStack. Services can be "SingleStack" (a single IP family),
// "PreferDualStack" (two IP families on dual-stack configured clusters or
// a single IP family on single-stack clusters), or "RequireDualStack"
// (two IP families on dual-stack configured clusters, otherwise fail). The
// ipFamilies and clusterIPs fields depend on the value of this field. This
// field will be wiped when updating a service to type ExternalName.
// +optional
optional string ipFamilyPolicy = 17;

View File

@ -4147,12 +4147,9 @@ type ServiceSpec struct {
// clients must ensure that clusterIPs[0] and clusterIP have the same
// value.
//
// Unless the "IPv6DualStack" feature gate is enabled, this field is
// limited to one value, which must be the same as the clusterIP field. If
// the feature gate is enabled, this field may hold a maximum of two
// entries (dual-stack IPs, in either order). These IPs must correspond to
// the values of the ipFamilies field. Both clusterIPs and ipFamilies are
// governed by the ipFamilyPolicy field.
// This field may hold a maximum of two entries (dual-stack IPs, in either order).
// These IPs must correspond to the values of the ipFamilies field. Both
// clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.
// More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
// +listType=atomic
// +optional
@ -4258,17 +4255,16 @@ type ServiceSpec struct {
// IPFamily *IPFamily `json:"ipFamily,omitempty" protobuf:"bytes,15,opt,name=ipFamily,Configcasttype=IPFamily"`
// IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this
// service, and is gated by the "IPv6DualStack" feature gate. This field
// is usually assigned automatically based on cluster configuration and the
// ipFamilyPolicy field. If this field is specified manually, the requested
// family is available in the cluster, and ipFamilyPolicy allows it, it
// will be used; otherwise creation of the service will fail. This field
// is conditionally mutable: it allows for adding or removing a secondary
// IP family, but it does not allow changing the primary IP family of the
// Service. Valid values are "IPv4" and "IPv6". This field only applies
// to Services of types ClusterIP, NodePort, and LoadBalancer, and does
// apply to "headless" services. This field will be wiped when updating a
// Service to type ExternalName.
// service. This field is usually assigned automatically based on cluster
// configuration and the ipFamilyPolicy field. If this field is specified
// manually, the requested family is available in the cluster,
// and ipFamilyPolicy allows it, it will be used; otherwise creation of
// the service will fail. This field is conditionally mutable: it allows
// for adding or removing a secondary IP family, but it does not allow
// changing the primary IP family of the Service. Valid values are "IPv4"
// and "IPv6". This field only applies to Services of types ClusterIP,
// NodePort, and LoadBalancer, and does apply to "headless" services.
// This field will be wiped when updating a Service to type ExternalName.
//
// This field may hold a maximum of two entries (dual-stack families, in
// either order). These families must correspond to the values of the
@ -4279,14 +4275,13 @@ type ServiceSpec struct {
IPFamilies []IPFamily `json:"ipFamilies,omitempty" protobuf:"bytes,19,opt,name=ipFamilies,casttype=IPFamily"`
// IPFamilyPolicy represents the dual-stack-ness requested or required by
// this Service, and is gated by the "IPv6DualStack" feature gate. If
// there is no value provided, then this field will be set to SingleStack.
// Services can be "SingleStack" (a single IP family), "PreferDualStack"
// (two IP families on dual-stack configured clusters or a single IP family
// on single-stack clusters), or "RequireDualStack" (two IP families on
// dual-stack configured clusters, otherwise fail). The ipFamilies and
// clusterIPs fields depend on the value of this field. This field will be
// wiped when updating a service to type ExternalName.
// this Service. If there is no value provided, then this field will be set
// to SingleStack. Services can be "SingleStack" (a single IP family),
// "PreferDualStack" (two IP families on dual-stack configured clusters or
// a single IP family on single-stack clusters), or "RequireDualStack"
// (two IP families on dual-stack configured clusters, otherwise fail). The
// ipFamilies and clusterIPs fields depend on the value of this field. This
// field will be wiped when updating a service to type ExternalName.
// +optional
IPFamilyPolicy *IPFamilyPolicyType `json:"ipFamilyPolicy,omitempty" protobuf:"bytes,17,opt,name=ipFamilyPolicy,casttype=IPFamilyPolicyType"`

View File

@ -2234,7 +2234,7 @@ var map_ServiceSpec = map[string]string{
"ports": "The list of ports that are exposed by this service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"selector": "Route service traffic to pods with label keys and values matching this selector. If empty or not present, the service is assumed to have an external process managing its endpoints, which Kubernetes will not modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/",
"clusterIP": "clusterIP is the IP address of the service and is usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be blank) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"clusterIPs": "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nUnless the \"IPv6DualStack\" feature gate is enabled, this field is limited to one value, which must be the same as the clusterIP field. If the feature gate is enabled, this field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"clusterIPs": "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nThis field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"type": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object or EndpointSlice objects. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a virtual IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the same endpoints as the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the same endpoints as the clusterIP. \"ExternalName\" aliases this service to the specified externalName. Several other fields do not apply to ExternalName services. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types",
"externalIPs": "externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system.",
"sessionAffinity": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
@ -2245,8 +2245,8 @@ var map_ServiceSpec = map[string]string{
"healthCheckNodePort": "healthCheckNodePort specifies the healthcheck nodePort for the service. This only applies when type is set to LoadBalancer and externalTrafficPolicy is set to Local. If a value is specified, is in-range, and is not in use, it will be used. If not specified, a value will be automatically allocated. External systems (e.g. load-balancers) can use this port to determine if a given node holds endpoints for this service or not. If this field is specified when creating a Service which does not need it, creation will fail. This field will be wiped when updating a Service to no longer need it (e.g. changing type).",
"publishNotReadyAddresses": "publishNotReadyAddresses indicates that any agent which deals with endpoints for this Service should disregard any indications of ready/not-ready. The primary use case for setting this field is for a StatefulSet's Headless Service to propagate SRV DNS records for its Pods for the purpose of peer discovery. The Kubernetes controllers that generate Endpoints and EndpointSlice resources for Services interpret this to mean that all endpoints are considered \"ready\" even if the Pods themselves are not. Agents which consume only Kubernetes generated endpoints through the Endpoints or EndpointSlice resources can safely assume this behavior.",
"sessionAffinityConfig": "sessionAffinityConfig contains the configurations of session affinity.",
"ipFamilies": "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service, and is gated by the \"IPv6DualStack\" feature gate. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.",
"ipFamilyPolicy": "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service, and is gated by the \"IPv6DualStack\" feature gate. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.",
"ipFamilies": "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.",
"ipFamilyPolicy": "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.",
"allocateLoadBalancerNodePorts": "allocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for services with type LoadBalancer. Default is \"true\". It may be set to \"false\" if the cluster load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a value), those requests will be respected, regardless of this field. This field may only be set for services with type LoadBalancer and will be cleared if the type is changed to any other type. This field is beta-level and is only honored by servers that enable the ServiceLBNodePortControl feature.",
"loadBalancerClass": "loadBalancerClass is the class of the load balancer implementation this Service belongs to. If specified, the value of this field must be a label-style identifier, with an optional prefix, e.g. \"internal-vip\" or \"example.com/internal-vip\". Unprefixed names are reserved for end-users. This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load balancer implementation is used, today this is typically done through the cloud provider integration, but should apply for any default implementation. If set, it is assumed that a load balancer implementation is watching for Services with a matching class. Any default load balancer implementation (e.g. cloud providers) should ignore Services that set this field. This field can only be set when creating or updating a Service to type 'LoadBalancer'. Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type.",
"internalTrafficPolicy": "InternalTrafficPolicy specifies if the cluster internal traffic should be routed to all endpoints or node-local endpoints only. \"Cluster\" routes internal traffic to a Service to all endpoints. \"Local\" routes traffic to node-local endpoints only, traffic is dropped if no node-local endpoints are ready. The default value is \"Cluster\".",

View File

@ -50,6 +50,6 @@ func SetupCurrentKubernetesSpecificFeatureGates(featuregates featuregate.Mutable
// cloudPublicFeatureGates consists of cloud-specific feature keys.
// To add a new feature, define a key for it at k8s.io/api/pkg/features and add it here.
var cloudPublicFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
IPv6DualStack: {Default: true, PreRelease: featuregate.Beta},
IPv6DualStack: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
ControllerManagerLeaderMigration: {Default: true, PreRelease: featuregate.Beta},
}

View File

@ -28,14 +28,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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"
restclient "k8s.io/client-go/rest"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/controller/endpoint"
"k8s.io/kubernetes/pkg/controller/endpointslice"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/test/integration/framework"
netutils "k8s.io/utils/net"
)
@ -48,9 +45,6 @@ func TestDualStackEndpoints(t *testing.T) {
return map[string]string{"foo": "bar"}
}
dualStack := true
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, dualStack)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
if err != nil {

View File

@ -39,10 +39,6 @@ import (
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/test/integration/framework"
netutils "k8s.io/utils/net"
)
@ -51,7 +47,6 @@ import (
func TestCreateServiceSingleStackIPv4(t *testing.T) {
// Create an IPv4 single stack control-plane
serviceCIDR := "10.0.0.0/16"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -268,7 +263,6 @@ func TestCreateServiceSingleStackIPv4(t *testing.T) {
func TestCreateServiceDualStackIPv6(t *testing.T) {
// Create an IPv6 only dual stack control-plane
serviceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -488,7 +482,6 @@ func TestCreateServiceDualStackIPv4IPv6(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16"
secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -715,7 +708,6 @@ func TestCreateServiceDualStackIPv6IPv4(t *testing.T) {
// Create an IPv6IPv4 dual stack control-plane
serviceCIDR := "2001:db8:1::/48"
secondaryServiceCIDR := "10.0.0.0/16"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -945,7 +937,6 @@ func TestUpgradeDowngrade(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16"
secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1060,7 +1051,6 @@ func TestConvertToFromExternalName(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16"
secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1148,92 +1138,11 @@ func TestConvertToFromExternalName(t *testing.T) {
}
}
// TestExistingServiceDefaulting tests that existing services are defaulted correctly after an upgrade
func TestExistingServiceDefaulting(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16"
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
if err != nil {
t.Fatalf("bad cidr: %v", err)
}
cfg.ExtraConfig.ServiceIPRange = *cidr
_, s, closeFn := framework.RunAnAPIServer(cfg)
defer closeFn()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL})
// Wait until the default "kubernetes" service is created.
if err = wait.Poll(250*time.Millisecond, time.Minute, func() (bool, error) {
_, err := client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), "kubernetes", metav1.GetOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return false, err
}
return !apierrors.IsNotFound(err), nil
}); err != nil {
t.Fatalf("creating kubernetes service timed out")
}
serviceName := "svc-ext-name"
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
Ports: []v1.ServicePort{
{
Port: 443,
TargetPort: intstr.FromInt(443),
},
},
},
}
// make sure gate is off (cluster is not running as dual stack)
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, false)()
// create a service
_, err = client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc, metav1.CreateOptions{})
if err != nil {
t.Fatalf("unexpected error while creating service:%v", err)
}
// validate the service was created correctly if it was not expected to fail
svc, err = client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), svc.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error to get the service %s %v", svc.Name, err)
}
// turn gate on
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
// validate the service was created correctly if it was not expected to fail
defaultedSvc, err := client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), svc.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error to get the service %s %v", svc.Name, err)
}
if defaultedSvc.Spec.IPFamilyPolicy == nil {
t.Fatalf("service should have an IPFamily Policy")
} else {
// must be equal to single stack
if *(defaultedSvc.Spec.IPFamilyPolicy) != v1.IPFamilyPolicySingleStack {
t.Fatalf("service should have a SingleStack as IPFamilyPolicy instead we got:%v", defaultedSvc.Spec.IPFamilyPolicy)
}
}
if err := validateServiceAndClusterIPFamily(defaultedSvc, []v1.IPFamily{v1.IPv4Protocol}); err != nil {
t.Fatalf("Unexpected error validating the service %s %v", svc.Name, err)
}
}
// TestPreferDualStack preferDualstack on create and update
func TestPreferDualStack(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16"
secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1318,7 +1227,6 @@ type labelsForMergePatch struct {
func TestServiceUpdate(t *testing.T) {
// Create an IPv4 single stack control-plane
serviceCIDR := "10.0.0.0/16"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, false)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1483,7 +1391,6 @@ func validateServiceAndClusterIPFamily(svc *v1.Service, expectedIPFamilies []v1.
func TestUpgradeServicePreferToDualStack(t *testing.T) {
// Create an IPv4 only dual stack control-plane
serviceCIDR := "192.168.0.0/24"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1580,7 +1487,7 @@ func TestDowngradeServicePreferToDualStack(t *testing.T) {
// Create a dual stack control-plane
serviceCIDR := "192.168.0.0/24"
secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
dualStackCfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
if err != nil {