mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Svc REST: Make ipFamilyPolicy authoritative
Previously we would try to infer the `ipFamilyPolicy` from `clusterIPs` and/or `ipFamilies`. That is too tricky. Now you MUST specify `ipFamilyPolicy` as one of the dual-stack options in order to get a dual-stack service.
This commit is contained in:
parent
ca8cfdcae9
commit
d30ae6a5ab
@ -824,42 +824,34 @@ func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) e
|
||||
return nil // nothing more to do.
|
||||
}
|
||||
|
||||
// Update-only prep work.
|
||||
if oldService != nil {
|
||||
np := service.Spec.IPFamilyPolicy
|
||||
|
||||
// If they didn't specify policy, or specified anything but
|
||||
// single-stack AND they reduced these fields, it's an error. They
|
||||
// need to specify policy.
|
||||
if np == nil || *np != api.IPFamilyPolicySingleStack {
|
||||
el := make(field.ErrorList, 0)
|
||||
|
||||
if reducedClusterIPs(oldService, service) {
|
||||
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy,
|
||||
"must be 'SingleStack' to release the secondary cluster IP"))
|
||||
}
|
||||
if reducedIPFamilies(oldService, service) {
|
||||
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy,
|
||||
"must be 'SingleStack' to release the secondary IP family"))
|
||||
}
|
||||
|
||||
if len(el) > 0 {
|
||||
return errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
||||
}
|
||||
} else { // policy must be SingleStack
|
||||
// Update: As long as ClusterIPs and IPFamilies have not changed,
|
||||
// setting policy to single-stack is clear intent.
|
||||
if *(service.Spec.IPFamilyPolicy) == api.IPFamilyPolicySingleStack {
|
||||
// ClusterIPs[0] is immutable, so it is safe to keep.
|
||||
if sameClusterIPs(oldService, service) && len(service.Spec.ClusterIPs) > 1 {
|
||||
service.Spec.ClusterIPs = service.Spec.ClusterIPs[0:1]
|
||||
}
|
||||
if sameIPFamilies(oldService, service) && len(service.Spec.IPFamilies) > 1 {
|
||||
service.Spec.IPFamilies = service.Spec.IPFamilies[0:1]
|
||||
}
|
||||
}
|
||||
// If the user didn't specify ipFamilyPolicy, we can infer a default. We
|
||||
// don't want a static default because we want to make sure that we never
|
||||
// change between single- and dual-stack modes with explicit direction, as
|
||||
// provided by ipFamilyPolicy. Consider these cases:
|
||||
// * Create (POST): If they didn't specify a policy we can assume it's
|
||||
// always SingleStack.
|
||||
// * Update (PUT): If they didn't specify a policy we need to adopt the
|
||||
// policy from before. This is better than always assuming SingleStack
|
||||
// because a PUT that changes clusterIPs from 2 to 1 value but doesn't
|
||||
// specify ipFamily would work.
|
||||
// * Update (PATCH): If they didn't specify a policy it will adopt the
|
||||
// policy from before.
|
||||
if service.Spec.IPFamilyPolicy == nil {
|
||||
if oldService != nil && oldService.Spec.IPFamilyPolicy != nil {
|
||||
// Update from an object with policy, use the old policy
|
||||
service.Spec.IPFamilyPolicy = oldService.Spec.IPFamilyPolicy
|
||||
} else if service.Spec.ClusterIP == api.ClusterIPNone && len(service.Spec.Selector) == 0 {
|
||||
// Special-case: headless + selectorless defaults to dual.
|
||||
requireDualStack := api.IPFamilyPolicyRequireDualStack
|
||||
service.Spec.IPFamilyPolicy = &requireDualStack
|
||||
} else {
|
||||
// create or update from an object without policy (e.g.
|
||||
// ExternalName) to one that needs policy
|
||||
singleStack := api.IPFamilyPolicySingleStack
|
||||
service.Spec.IPFamilyPolicy = &singleStack
|
||||
}
|
||||
}
|
||||
// Henceforth we can assume ipFamilyPolicy is set.
|
||||
|
||||
// Do some loose pre-validation of the input. This makes it easier in the
|
||||
// rest of allocation code to not have to consider corner cases.
|
||||
@ -872,6 +864,32 @@ func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) e
|
||||
//TODO(thockin): Move this logic to validation?
|
||||
el := make(field.ErrorList, 0)
|
||||
|
||||
// Update-only prep work.
|
||||
if oldService != nil {
|
||||
if getIPFamilyPolicy(service) == api.IPFamilyPolicySingleStack {
|
||||
// As long as ClusterIPs and IPFamilies have not changed, setting
|
||||
// the policy to single-stack is clear intent.
|
||||
// ClusterIPs[0] is immutable, so it is safe to keep.
|
||||
if sameClusterIPs(oldService, service) && len(service.Spec.ClusterIPs) > 1 {
|
||||
service.Spec.ClusterIPs = service.Spec.ClusterIPs[0:1]
|
||||
}
|
||||
if sameIPFamilies(oldService, service) && len(service.Spec.IPFamilies) > 1 {
|
||||
service.Spec.IPFamilies = service.Spec.IPFamilies[0:1]
|
||||
}
|
||||
} else {
|
||||
// If the policy is anything but single-stack AND they reduced these
|
||||
// fields, it's an error. They need to specify policy.
|
||||
if reducedClusterIPs(oldService, service) {
|
||||
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy,
|
||||
"must be 'SingleStack' to release the secondary cluster IP"))
|
||||
}
|
||||
if reducedIPFamilies(oldService, service) {
|
||||
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy,
|
||||
"must be 'SingleStack' to release the secondary IP family"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure ipFamilyPolicy makes sense for the provided ipFamilies and
|
||||
// clusterIPs. Further checks happen below - after the special cases.
|
||||
if getIPFamilyPolicy(service) == api.IPFamilyPolicySingleStack {
|
||||
@ -916,23 +934,10 @@ func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) e
|
||||
return errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
||||
}
|
||||
|
||||
// Infer IPFamilyPolicy from IPFamilies[]. This block does not handle the
|
||||
// final defaulting - that happens a bit later, after special cases.
|
||||
if service.Spec.IPFamilyPolicy == nil && len(service.Spec.IPFamilies) == 2 {
|
||||
requireDualStack := api.IPFamilyPolicyRequireDualStack
|
||||
service.Spec.IPFamilyPolicy = &requireDualStack
|
||||
}
|
||||
|
||||
// Special-case: headless + selectorless. This has to happen before other
|
||||
// checks because it explicitly allows combinations of inputs that would
|
||||
// otherwise be errors.
|
||||
if len(service.Spec.ClusterIPs) > 0 && service.Spec.ClusterIPs[0] == api.ClusterIPNone && len(service.Spec.Selector) == 0 {
|
||||
// If the use said nothing about policy and we can't infer it, they get dual-stack
|
||||
if service.Spec.IPFamilyPolicy == nil {
|
||||
requireDualStack := api.IPFamilyPolicyRequireDualStack
|
||||
service.Spec.IPFamilyPolicy = &requireDualStack
|
||||
}
|
||||
|
||||
if service.Spec.ClusterIP == api.ClusterIPNone && len(service.Spec.Selector) == 0 {
|
||||
// If IPFamilies was not set by the user, start with the default
|
||||
// family.
|
||||
if len(service.Spec.IPFamilies) == 0 {
|
||||
@ -981,13 +986,6 @@ func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) e
|
||||
return errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
||||
}
|
||||
|
||||
// Finally, if IPFamilyPolicy is *still* not set, we can default it to
|
||||
// SingleStack. If there are any webhooks, they have already run.
|
||||
if service.Spec.IPFamilyPolicy == nil {
|
||||
singleStack := api.IPFamilyPolicySingleStack
|
||||
service.Spec.IPFamilyPolicy = &singleStack
|
||||
}
|
||||
|
||||
// nil families, gets cluster default
|
||||
if len(service.Spec.IPFamilies) == 0 {
|
||||
service.Spec.IPFamilies = []api.IPFamily{al.defaultServiceIPFamily}
|
||||
|
@ -349,6 +349,7 @@ func TestServiceRegistryUpdateUnspecifiedAllocations(t *testing.T) {
|
||||
create: svctest.MakeService("foo",
|
||||
svctest.SetTypeLoadBalancer,
|
||||
svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal),
|
||||
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
|
||||
svctest.SetClusterIPs("1.2.3.4", "2000::1"),
|
||||
svctest.SetPorts(
|
||||
svctest.MakeServicePort("p", 867, intstr.FromInt(867), api.ProtocolTCP),
|
||||
@ -372,6 +373,7 @@ func TestServiceRegistryUpdateUnspecifiedAllocations(t *testing.T) {
|
||||
create: svctest.MakeService("foo",
|
||||
svctest.SetTypeLoadBalancer,
|
||||
svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal),
|
||||
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
|
||||
svctest.SetClusterIPs("1.2.3.4", "2000::1"),
|
||||
svctest.SetPorts(
|
||||
svctest.MakeServicePort("p", 867, intstr.FromInt(867), api.ProtocolTCP),
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -367,7 +367,7 @@ var _ = common.SIGDescribe("[Feature:IPv6DualStack]", func() {
|
||||
expectedPolicy := v1.IPFamilyPolicyRequireDualStack
|
||||
expectedFamilies := []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol}
|
||||
|
||||
service := createService(t.ServiceName, t.Namespace, t.Labels, nil, expectedFamilies)
|
||||
service := createService(t.ServiceName, t.Namespace, t.Labels, &expectedPolicy, expectedFamilies)
|
||||
|
||||
jig.Labels = t.Labels
|
||||
err := jig.CreateServicePods(2)
|
||||
@ -412,7 +412,7 @@ var _ = common.SIGDescribe("[Feature:IPv6DualStack]", func() {
|
||||
expectedPolicy := v1.IPFamilyPolicyRequireDualStack
|
||||
expectedFamilies := []v1.IPFamily{v1.IPv6Protocol, v1.IPv4Protocol}
|
||||
|
||||
service := createService(t.ServiceName, t.Namespace, t.Labels, nil, expectedFamilies)
|
||||
service := createService(t.ServiceName, t.Namespace, t.Labels, &expectedPolicy, expectedFamilies)
|
||||
|
||||
jig.Labels = t.Labels
|
||||
err := jig.CreateServicePods(2)
|
||||
|
Loading…
Reference in New Issue
Block a user