Commit 82cde218 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #44741 from MrHohn/esipp-validation-refine

Automatic merge from submit-queue Refine ESIPP validation logic in validation.go Separated from #41162. The previous ESIPP validation logic in validation.go has a huge overlap with [function healthCheckNodePortUpdate in service/rest.go](https://github.com/kubernetes/kubernetes/blob/870585e8e1ca1133a67d4d44bfd4a9722b2b93c7/pkg/registry/core/service/rest.go#L283-L373), in which we reject any invalid modifications on ESIPP annotations. This PR removes the overlap, and make validation.go only check if values are legal and whether user mixes different API versions (alpha & beta). We are indeed removing the alpha annotation support, but it is kept in the codes for the ease of transiting the same logic onto beta/GA. /assign @thockin @freehan **Release note**: ```release-note NONE ```
parents 45d96c72 a95c71e8
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"path" "path"
"reflect" "reflect"
"regexp" "regexp"
"strconv"
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
...@@ -2569,13 +2570,6 @@ var supportedServiceType = sets.NewString(string(api.ServiceTypeClusterIP), stri ...@@ -2569,13 +2570,6 @@ var supportedServiceType = sets.NewString(string(api.ServiceTypeClusterIP), stri
// ValidateService tests if required fields/annotations of a Service are valid. // ValidateService tests if required fields/annotations of a Service are valid.
func ValidateService(service *api.Service) field.ErrorList { func ValidateService(service *api.Service) field.ErrorList {
allErrs := validateServiceFields(service)
allErrs = append(allErrs, validateServiceAnnotations(service, nil)...)
return allErrs
}
// validateServiceFields tests if required fields in the service are set.
func validateServiceFields(service *api.Service) field.ErrorList {
allErrs := ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName, field.NewPath("metadata")) allErrs := ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName, field.NewPath("metadata"))
specPath := field.NewPath("spec") specPath := field.NewPath("spec")
...@@ -2719,6 +2713,9 @@ func validateServiceFields(service *api.Service) field.ErrorList { ...@@ -2719,6 +2713,9 @@ func validateServiceFields(service *api.Service) field.ErrorList {
allErrs = append(allErrs, field.Invalid(fieldPath, val, "must be a list of IP ranges. For example, 10.240.0.0/24,10.250.0.0/24 ")) allErrs = append(allErrs, field.Invalid(fieldPath, val, "must be a list of IP ranges. For example, 10.240.0.0/24,10.250.0.0/24 "))
} }
} }
allErrs = append(allErrs, validateServiceExternalTrafficFields(service)...)
return allErrs return allErrs
} }
...@@ -2760,61 +2757,84 @@ func validateServicePort(sp *api.ServicePort, requireName, isHeadlessService boo ...@@ -2760,61 +2757,84 @@ func validateServicePort(sp *api.ServicePort, requireName, isHeadlessService boo
return allErrs return allErrs
} }
func validateServiceAnnotations(service *api.Service, oldService *api.Service) (allErrs field.ErrorList) { // validateServiceExternalTrafficFields validates ExternalTraffic related annotations
// 2 annotations went from alpha to beta in 1.5: healthcheck-nodeport and // have legal value.
// external-traffic. The user cannot mix these. All updates to the alpha func validateServiceExternalTrafficFields(service *api.Service) field.ErrorList {
// annotation are disallowed. The user must change both alpha annotations allErrs := field.ErrorList{}
// to beta before making any modifications, even though the system continues
// to respect the alpha version. for _, annotation := range []string{apiservice.AlphaAnnotationExternalTraffic, apiservice.BetaAnnotationExternalTraffic} {
hcAlpha, healthCheckAlphaOk := service.Annotations[apiservice.AlphaAnnotationHealthCheckNodePort] if l, ok := service.Annotations[annotation]; ok {
onlyLocalAlpha, onlyLocalAlphaOk := service.Annotations[apiservice.AlphaAnnotationExternalTraffic] if l != apiservice.AnnotationValueExternalTrafficLocal &&
l != apiservice.AnnotationValueExternalTrafficGlobal {
_, healthCheckBetaOk := service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort] allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(annotation), l,
_, onlyLocalBetaOk := service.Annotations[apiservice.BetaAnnotationExternalTraffic] fmt.Sprintf("ExternalTraffic must be %v or %v", apiservice.AnnotationValueExternalTrafficLocal, apiservice.AnnotationValueExternalTrafficGlobal)))
}
var oldHealthCheckAlpha, oldOnlyLocalAlpha string }
var oldHealthCheckAlphaOk, oldOnlyLocalAlphaOk bool }
if oldService != nil { for _, annotation := range []string{apiservice.AlphaAnnotationHealthCheckNodePort, apiservice.BetaAnnotationHealthCheckNodePort} {
oldHealthCheckAlpha, oldHealthCheckAlphaOk = oldService.Annotations[apiservice.AlphaAnnotationHealthCheckNodePort] if l, ok := service.Annotations[annotation]; ok {
oldOnlyLocalAlpha, oldOnlyLocalAlphaOk = oldService.Annotations[apiservice.AlphaAnnotationExternalTraffic] p, err := strconv.Atoi(l)
} if err != nil {
hcValueChanged := oldHealthCheckAlphaOk && healthCheckAlphaOk && oldHealthCheckAlpha != hcAlpha allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(annotation), l,
hcValueNew := !oldHealthCheckAlphaOk && healthCheckAlphaOk "HealthCheckNodePort must be a valid port number"))
hcValueGone := !healthCheckAlphaOk && !healthCheckBetaOk && oldHealthCheckAlphaOk } else if p <= 0 {
onlyLocalHCMismatch := onlyLocalBetaOk && healthCheckAlphaOk allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(annotation), l,
"HealthCheckNodePort must be greater than 0"))
// On upgrading to a 1.5 cluster, the user is locked in at the current }
// alpha setting, till they modify the Service such that the pair of }
// annotations are both beta. Basically this means we need to:
// Disallow updates to the alpha annotation.
// Disallow creating a Service with the alpha annotation.
// Disallow removing both alpha annotations. Removing the health-check
// annotation is rejected at a later stage anyway, so if we allow removing
// just onlyLocal we might leak the port.
// Disallow a single field from transitioning to beta. Mismatched annotations
// cause confusion.
// Ignore changes to the fields if they're both transitioning to beta.
// Allow modifications to Services in fields other than the alpha annotation.
if hcValueNew || hcValueChanged || hcValueGone || onlyLocalHCMismatch {
fieldPath := field.NewPath("metadata", "annotations").Key(apiservice.AlphaAnnotationHealthCheckNodePort)
msg := fmt.Sprintf("please replace the alpha annotation with the beta version %v",
apiservice.BetaAnnotationHealthCheckNodePort)
allErrs = append(allErrs, field.Invalid(fieldPath, apiservice.AlphaAnnotationHealthCheckNodePort, msg))
} }
onlyLocalValueChanged := oldOnlyLocalAlphaOk && onlyLocalAlphaOk && oldOnlyLocalAlpha != onlyLocalAlpha allErrs = append(allErrs, validateServiceExternalTrafficAPIVersion(service)...)
onlyLocalValueNew := !oldOnlyLocalAlphaOk && onlyLocalAlphaOk
onlyLocalValueGone := !onlyLocalAlphaOk && !onlyLocalBetaOk && oldOnlyLocalAlphaOk return allErrs
hcOnlyLocalMismatch := onlyLocalAlphaOk && healthCheckBetaOk }
// serviceExternalTrafficStatus stores flags indicating whether ExternalTraffic
// related beta annotations and alpha annotations are set on service.
type serviceExternalTrafficStatus struct {
alphaExternalTrafficIsSet bool
alphaHealthCheckIsSet bool
betaExternalTrafficIsSet bool
betaHealthCheckIsSet bool
}
if onlyLocalValueNew || onlyLocalValueChanged || onlyLocalValueGone || hcOnlyLocalMismatch { func (s *serviceExternalTrafficStatus) useAlphaExternalTrafficWithBeta() bool {
return s.alphaExternalTrafficIsSet && (s.betaExternalTrafficIsSet || s.betaHealthCheckIsSet)
}
func (s *serviceExternalTrafficStatus) useAlphaHealthCheckWithBeta() bool {
return s.alphaHealthCheckIsSet && (s.betaExternalTrafficIsSet || s.betaHealthCheckIsSet)
}
func getServiceExternalTrafficStatus(service *api.Service) *serviceExternalTrafficStatus {
s := serviceExternalTrafficStatus{}
_, s.alphaExternalTrafficIsSet = service.Annotations[apiservice.AlphaAnnotationExternalTraffic]
_, s.alphaHealthCheckIsSet = service.Annotations[apiservice.AlphaAnnotationHealthCheckNodePort]
_, s.betaExternalTrafficIsSet = service.Annotations[apiservice.BetaAnnotationExternalTraffic]
_, s.betaHealthCheckIsSet = service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort]
return &s
}
// validateServiceExternalTrafficAPIVersion checks if user mixes ExternalTraffic
// API versions.
func validateServiceExternalTrafficAPIVersion(service *api.Service) field.ErrorList {
allErrs := field.ErrorList{}
status := getServiceExternalTrafficStatus(service)
if status.useAlphaExternalTrafficWithBeta() {
fieldPath := field.NewPath("metadata", "annotations").Key(apiservice.AlphaAnnotationExternalTraffic) fieldPath := field.NewPath("metadata", "annotations").Key(apiservice.AlphaAnnotationExternalTraffic)
msg := fmt.Sprintf("please replace the alpha annotation with the beta version %v", msg := fmt.Sprintf("please replace the alpha annotation with beta annotation")
apiservice.BetaAnnotationExternalTraffic)
allErrs = append(allErrs, field.Invalid(fieldPath, apiservice.AlphaAnnotationExternalTraffic, msg)) allErrs = append(allErrs, field.Invalid(fieldPath, apiservice.AlphaAnnotationExternalTraffic, msg))
} }
return
if status.useAlphaHealthCheckWithBeta() {
fieldPath := field.NewPath("metadata", "annotations").Key(apiservice.AlphaAnnotationHealthCheckNodePort)
msg := fmt.Sprintf("please replace the alpha annotation with beta annotation")
allErrs = append(allErrs, field.Invalid(fieldPath, apiservice.AlphaAnnotationHealthCheckNodePort, msg))
}
return allErrs
} }
// ValidateServiceUpdate tests if required fields in the service are set during an update // ValidateServiceUpdate tests if required fields in the service are set during an update
...@@ -2829,8 +2849,7 @@ func ValidateServiceUpdate(service, oldService *api.Service) field.ErrorList { ...@@ -2829,8 +2849,7 @@ func ValidateServiceUpdate(service, oldService *api.Service) field.ErrorList {
} }
} }
allErrs = append(allErrs, validateServiceFields(service)...) allErrs = append(allErrs, ValidateService(service)...)
allErrs = append(allErrs, validateServiceAnnotations(service, oldService)...)
return allErrs return allErrs
} }
......
...@@ -5622,10 +5622,36 @@ func TestValidateService(t *testing.T) { ...@@ -5622,10 +5622,36 @@ func TestValidateService(t *testing.T) {
numErrs: 1, numErrs: 1,
}, },
{ {
name: "LoadBalancer disallows onlyLocal alpha annotations", name: "LoadBalancer allows onlyLocal alpha annotations",
tweakSvc: func(s *api.Service) { tweakSvc: func(s *api.Service) {
s.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal s.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
}, },
numErrs: 0,
},
{
name: "invalid externalTraffic beta annotation",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Annotations[service.BetaAnnotationExternalTraffic] = "invalid"
},
numErrs: 1,
},
{
name: "nagative healthCheckNodePort beta annotation",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
s.Annotations[service.BetaAnnotationHealthCheckNodePort] = "-1"
},
numErrs: 1,
},
{
name: "invalid healthCheckNodePort beta annotation",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
s.Annotations[service.BetaAnnotationHealthCheckNodePort] = "whatisthis"
},
numErrs: 1, numErrs: 1,
}, },
{ {
...@@ -7028,22 +7054,22 @@ func TestValidateServiceUpdate(t *testing.T) { ...@@ -7028,22 +7054,22 @@ func TestValidateServiceUpdate(t *testing.T) {
numErrs: 1, numErrs: 1,
}, },
{ {
name: "Service disallows removing one onlyLocal alpha annotation", name: "Service allows removing onlyLocal alpha annotations",
tweakSvc: func(oldSvc, newSvc *api.Service) { tweakSvc: func(oldSvc, newSvc *api.Service) {
oldSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal oldSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = "3001" oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = "3001"
}, },
numErrs: 2, numErrs: 0,
}, },
{ {
name: "Service disallows modifying onlyLocal alpha annotations", name: "Service allows modifying onlyLocal alpha annotations",
tweakSvc: func(oldSvc, newSvc *api.Service) { tweakSvc: func(oldSvc, newSvc *api.Service) {
oldSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal oldSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = "3001" oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = "3001"
newSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficGlobal newSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficGlobal
newSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] newSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort]
}, },
numErrs: 1, numErrs: 0,
}, },
{ {
name: "Service disallows promoting one of the onlyLocal pair to beta", name: "Service disallows promoting one of the onlyLocal pair to beta",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment