ingress: Add Ingress to v1 API and update backend to defaultBackend

ingress: use new serviceBackend split

ingress: remove all v1beta1 restrictions on creation

This change removes creation and update restrictions enforced by
k8s 1.18 for not allowing resource backends.

Paths are no longer
required to be valid regex and a PathType is now user-specified
and no longer defaulted.

Also remove all TODOs in staging/net/v1 types

Signed-off-by: Christopher M. Luciano <cmluciano@us.ibm.com>
This commit is contained in:
Christopher M. Luciano
2020-04-01 14:51:04 -04:00
parent 0f2cccc98c
commit 2b091f60ca
55 changed files with 9524 additions and 282 deletions

View File

@@ -43,6 +43,8 @@ go_library(
"//pkg/apis/core/validation:go_default_library",
"//pkg/apis/networking:go_default_library",
"//pkg/features:go_default_library",
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
"//staging/src/k8s.io/api/networking/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/validation/path:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",

View File

@@ -21,6 +21,8 @@ import (
"net"
"strings"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
pathvalidation "k8s.io/apimachinery/pkg/api/validation/path"
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
@@ -251,18 +253,29 @@ func validateIngressTLS(spec *networking.IngressSpec, fldPath *field.Path) field
return allErrs
}
// defaultBackendFieldName returns the name of the field used for defaultBackend
// in the provided GroupVersion.
func defaultBackendFieldName(gv schema.GroupVersion) string {
switch gv {
case networkingv1beta1.SchemeGroupVersion, extensionsv1beta1.SchemeGroupVersion:
return "backend"
default:
return "defaultBackend"
}
}
// ValidateIngressSpec tests if required fields in the IngressSpec are set.
func ValidateIngressSpec(spec *networking.IngressSpec, fldPath *field.Path, opts IngressValidationOptions, requestGV schema.GroupVersion) field.ErrorList {
allErrs := field.ErrorList{}
if len(spec.Rules) == 0 && spec.Backend == nil {
errMsg := "either `backend` or `rules` must be specified"
if len(spec.Rules) == 0 && spec.DefaultBackend == nil {
errMsg := fmt.Sprintf("either `%s` or `rules` must be specified", defaultBackendFieldName(requestGV))
allErrs = append(allErrs, field.Invalid(fldPath, spec.Rules, errMsg))
}
if spec.Backend != nil {
allErrs = append(allErrs, validateIngressBackend(spec.Backend, fldPath.Child("backend"), opts)...)
if spec.DefaultBackend != nil {
allErrs = append(allErrs, validateIngressBackend(spec.DefaultBackend, fldPath.Child(defaultBackendFieldName(requestGV)), opts, requestGV)...)
}
if len(spec.Rules) > 0 {
allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"), opts)...)
allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"), opts, requestGV)...)
}
if len(spec.TLS) > 0 {
allErrs = append(allErrs, validateIngressTLS(spec, fldPath.Child("tls"))...)
@@ -282,7 +295,7 @@ func ValidateIngressStatusUpdate(ingress, oldIngress *networking.Ingress) field.
return allErrs
}
func validateIngressRules(ingressRules []networking.IngressRule, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
func validateIngressRules(ingressRules []networking.IngressRule, fldPath *field.Path, opts IngressValidationOptions, requestGV schema.GroupVersion) field.ErrorList {
allErrs := field.ErrorList{}
if len(ingressRules) == 0 {
return append(allErrs, field.Required(fldPath, ""))
@@ -304,31 +317,31 @@ func validateIngressRules(ingressRules []networking.IngressRule, fldPath *field.
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg))
}
}
allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(0), opts)...)
allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(0), opts, requestGV)...)
}
return allErrs
}
func validateIngressRuleValue(ingressRule *networking.IngressRuleValue, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
func validateIngressRuleValue(ingressRule *networking.IngressRuleValue, fldPath *field.Path, opts IngressValidationOptions, requestGV schema.GroupVersion) field.ErrorList {
allErrs := field.ErrorList{}
if ingressRule.HTTP != nil {
allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"), opts)...)
allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"), opts, requestGV)...)
}
return allErrs
}
func validateHTTPIngressRuleValue(httpIngressRuleValue *networking.HTTPIngressRuleValue, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
func validateHTTPIngressRuleValue(httpIngressRuleValue *networking.HTTPIngressRuleValue, fldPath *field.Path, opts IngressValidationOptions, requestGV schema.GroupVersion) field.ErrorList {
allErrs := field.ErrorList{}
if len(httpIngressRuleValue.Paths) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("paths"), ""))
}
for i, path := range httpIngressRuleValue.Paths {
allErrs = append(allErrs, validateHTTPIngressPath(&path, fldPath.Child("paths").Index(i), opts)...)
allErrs = append(allErrs, validateHTTPIngressPath(&path, fldPath.Child("paths").Index(i), opts, requestGV)...)
}
return allErrs
}
func validateHTTPIngressPath(path *networking.HTTPIngressPath, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
func validateHTTPIngressPath(path *networking.HTTPIngressPath, fldPath *field.Path, opts IngressValidationOptions, requestGV schema.GroupVersion) field.ErrorList {
allErrs := field.ErrorList{}
if path.PathType == nil {
@@ -362,32 +375,82 @@ func validateHTTPIngressPath(path *networking.HTTPIngressPath, fldPath *field.Pa
default:
allErrs = append(allErrs, field.NotSupported(fldPath.Child("pathType"), *path.PathType, supportedPathTypes.List()))
}
allErrs = append(allErrs, validateIngressBackend(&path.Backend, fldPath.Child("backend"), opts)...)
allErrs = append(allErrs, validateIngressBackend(&path.Backend, fldPath.Child("backend"), opts, requestGV)...)
return allErrs
}
// numberPortField returns the field path to a service port number field
// relative to a backend struct in the provided GroupVersion
func numberPortField(numberPortFieldPath *field.Path, gv schema.GroupVersion) *field.Path {
switch gv {
case networkingv1beta1.SchemeGroupVersion, extensionsv1beta1.SchemeGroupVersion:
return numberPortFieldPath.Child("servicePort")
default:
return numberPortFieldPath.Child("service", "port", "number")
}
}
// namedPortField returns the field path to a service port name field
// relative to a backend struct in the provided GroupVersion
func namedPortField(namedPortFieldPath *field.Path, gv schema.GroupVersion) *field.Path {
switch gv {
case networkingv1beta1.SchemeGroupVersion, extensionsv1beta1.SchemeGroupVersion:
return namedPortFieldPath.Child("servicePort")
default:
return namedPortFieldPath.Child("service", "port", "name")
}
}
// serviceNameFieldPath returns the name of the field used for a
// service name in the provided GroupVersion.
func serviceNameFieldPath(backendFieldPath *field.Path, gv schema.GroupVersion) *field.Path {
switch gv {
case networkingv1beta1.SchemeGroupVersion, extensionsv1beta1.SchemeGroupVersion:
return backendFieldPath.Child("serviceName")
default:
return backendFieldPath.Child("service", "name")
}
}
// validateIngressBackend tests if a given backend is valid.
func validateIngressBackend(backend *networking.IngressBackend, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
func validateIngressBackend(backend *networking.IngressBackend, fldPath *field.Path, opts IngressValidationOptions, requestGV schema.GroupVersion) field.ErrorList {
allErrs := field.ErrorList{}
hasResourceBackend := backend.Resource != nil
hasServiceBackend := len(backend.ServiceName) > 0 || backend.ServicePort.IntVal != 0 || len(backend.ServicePort.StrVal) > 0
hasServiceBackend := backend.Service != nil
switch {
case hasResourceBackend && hasServiceBackend:
return append(allErrs, field.Invalid(fldPath, "", "cannot set both resource and service backends"))
case hasResourceBackend:
allErrs = append(allErrs, validateIngressTypedLocalObjectReference(backend.Resource, fldPath.Child("resource"))...)
default:
if len(backend.ServiceName) == 0 {
return append(allErrs, field.Required(fldPath.Child("serviceName"), ""))
}
for _, msg := range apivalidation.ValidateServiceName(backend.ServiceName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceName"), backend.ServiceName, msg))
}
allErrs = append(allErrs, apivalidation.ValidatePortNumOrName(backend.ServicePort, fldPath.Child("servicePort"))...)
case hasServiceBackend:
if len(backend.Service.Name) == 0 {
allErrs = append(allErrs, field.Required(serviceNameFieldPath(fldPath, requestGV), ""))
} else {
for _, msg := range apivalidation.ValidateServiceName(backend.Service.Name, false) {
allErrs = append(allErrs, field.Invalid(serviceNameFieldPath(fldPath, requestGV), backend.Service.Name, msg))
}
}
hasPortName := len(backend.Service.Port.Name) > 0
hasPortNumber := backend.Service.Port.Number != 0
if hasPortName && hasPortNumber {
allErrs = append(allErrs, field.Invalid(fldPath, "", "cannot set both port name & port number"))
} else if hasPortName {
for _, msg := range validation.IsValidPortName(backend.Service.Port.Name) {
allErrs = append(allErrs, field.Invalid(namedPortField(fldPath, requestGV), backend.Service.Port.Name, msg))
}
} else if hasPortNumber {
for _, msg := range validation.IsValidPortNum(int(backend.Service.Port.Number)) {
allErrs = append(allErrs, field.Invalid(numberPortField(fldPath, requestGV), backend.Service.Port.Number, msg))
}
} else {
allErrs = append(allErrs, field.Required(fldPath, "port name or number is required"))
}
default:
allErrs = append(allErrs, field.Invalid(fldPath, "", "resource or service backend is required"))
}
return allErrs
}

View File

@@ -896,9 +896,15 @@ func TestValidateNetworkPolicyUpdate(t *testing.T) {
}
func TestValidateIngress(t *testing.T) {
serviceBackend := &networking.IngressServiceBackend{
Name: "defaultbackend",
Port: networking.ServiceBackendPort{
Name: "",
Number: 80,
},
}
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
Service: serviceBackend,
}
pathTypePrefix := networking.PathTypePrefix
pathTypeImplementationSpecific := networking.PathTypeImplementationSpecific
@@ -910,10 +916,7 @@ func TestValidateIngress(t *testing.T) {
Namespace: metav1.NamespaceDefault,
},
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
DefaultBackend: &defaultBackend,
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
@@ -961,7 +964,7 @@ func TestValidateIngress(t *testing.T) {
"backend (v1beta1) with no service": {
groupVersion: &networkingv1beta1.SchemeGroupVersion,
tweakIngress: func(ing *networking.Ingress) {
ing.Spec.Backend.ServiceName = ""
ing.Spec.DefaultBackend.Service.Name = ""
},
expectErrsOnFields: []string{
"spec.backend.serviceName",
@@ -1039,7 +1042,7 @@ func TestValidateIngress(t *testing.T) {
Path: "/foo",
PathType: &pathTypeImplementationSpecific,
Backend: networking.IngressBackend{
ServiceName: "default-backend",
Service: serviceBackend,
Resource: &api.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("example.com"),
Kind: "foo",
@@ -1064,7 +1067,7 @@ func TestValidateIngress(t *testing.T) {
Path: "/foo",
PathType: &pathTypeImplementationSpecific,
Backend: networking.IngressBackend{
ServicePort: intstr.FromInt(80),
Service: serviceBackend,
Resource: &api.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("example.com"),
Kind: "foo",
@@ -1083,8 +1086,8 @@ func TestValidateIngress(t *testing.T) {
"spec.backend resource and service name are not allowed together": {
groupVersion: &networkingv1beta1.SchemeGroupVersion,
tweakIngress: func(ing *networking.Ingress) {
ing.Spec.Backend = &networking.IngressBackend{
ServiceName: "default-backend",
ing.Spec.DefaultBackend = &networking.IngressBackend{
Service: serviceBackend,
Resource: &api.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("example.com"),
Kind: "foo",
@@ -1099,8 +1102,8 @@ func TestValidateIngress(t *testing.T) {
"spec.backend resource and service port are not allowed together": {
groupVersion: &networkingv1beta1.SchemeGroupVersion,
tweakIngress: func(ing *networking.Ingress) {
ing.Spec.Backend = &networking.IngressBackend{
ServicePort: intstr.FromInt(80),
ing.Spec.DefaultBackend = &networking.IngressBackend{
Service: serviceBackend,
Resource: &api.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("example.com"),
Kind: "foo",
@@ -1136,8 +1139,16 @@ func TestValidateIngress(t *testing.T) {
}
func TestValidateIngressRuleValue(t *testing.T) {
serviceBackend := networking.IngressServiceBackend{
Name: "defaultbackend",
Port: networking.ServiceBackendPort{
Name: "",
Number: 80,
},
}
fldPath := field.NewPath("testing.http.paths[0].path")
testCases := map[string]struct {
groupVersion *schema.GroupVersion
pathType networking.PathType
path string
expectedErrs field.ErrorList
@@ -1233,16 +1244,17 @@ func TestValidateIngressRuleValue(t *testing.T) {
Path: testCase.path,
PathType: &testCase.pathType,
Backend: networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
Service: &serviceBackend,
},
},
},
},
}
errs := validateIngressRuleValue(irv, field.NewPath("testing"), IngressValidationOptions{})
gv := testCase.groupVersion
if gv == nil {
gv = &networkingv1.SchemeGroupVersion
}
errs := validateIngressRuleValue(irv, field.NewPath("testing"), IngressValidationOptions{}, *gv)
if len(errs) != len(testCase.expectedErrs) {
t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
}
@@ -1258,9 +1270,14 @@ func TestValidateIngressRuleValue(t *testing.T) {
func TestValidateIngressCreate(t *testing.T) {
implementationPathType := networking.PathTypeImplementationSpecific
serviceBackend := &networking.IngressServiceBackend{
Name: "defaultbackend",
Port: networking.ServiceBackendPort{
Number: 80,
},
}
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
Service: serviceBackend,
}
resourceBackend := &api.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("example.com"),
@@ -1274,12 +1291,13 @@ func TestValidateIngressCreate(t *testing.T) {
ResourceVersion: "1234",
},
Spec: networking.IngressSpec{
Backend: &defaultBackend,
Rules: []networking.IngressRule{},
DefaultBackend: &defaultBackend,
Rules: []networking.IngressRule{},
},
}
testCases := map[string]struct {
groupVersion *schema.GroupVersion
tweakIngress func(ingress *networking.Ingress)
expectedErrs field.ErrorList
}{
@@ -1319,7 +1337,7 @@ func TestValidateIngressCreate(t *testing.T) {
},
expectedErrs: field.ErrorList{},
},
"invalid regex path": {
"invalid regex path allowed (v1)": {
tweakIngress: func(ingress *networking.Ingress) {
ingress.Spec.Rules = []networking.IngressRule{{
Host: "foo.bar.com",
@@ -1338,7 +1356,7 @@ func TestValidateIngressCreate(t *testing.T) {
},
"Spec.Backend.Resource field allowed on create": {
tweakIngress: func(ingress *networking.Ingress) {
ingress.Spec.Backend = &networking.IngressBackend{
ingress.Spec.DefaultBackend = &networking.IngressBackend{
Resource: resourceBackend}
},
expectedErrs: field.ErrorList{},
@@ -1367,8 +1385,11 @@ func TestValidateIngressCreate(t *testing.T) {
t.Run(name, func(t *testing.T) {
newIngress := baseIngress.DeepCopy()
testCase.tweakIngress(newIngress)
errs := ValidateIngressCreate(newIngress, networkingv1beta1.SchemeGroupVersion)
gv := testCase.groupVersion
if gv == nil {
gv = &networkingv1.SchemeGroupVersion
}
errs := ValidateIngressCreate(newIngress, *gv)
if len(errs) != len(testCase.expectedErrs) {
t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
}
@@ -1384,9 +1405,14 @@ func TestValidateIngressCreate(t *testing.T) {
func TestValidateIngressUpdate(t *testing.T) {
implementationPathType := networking.PathTypeImplementationSpecific
serviceBackend := &networking.IngressServiceBackend{
Name: "defaultbackend",
Port: networking.ServiceBackendPort{
Number: 80,
},
}
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
Service: serviceBackend,
}
resourceBackend := &api.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("example.com"),
@@ -1400,7 +1426,7 @@ func TestValidateIngressUpdate(t *testing.T) {
ResourceVersion: "1234",
},
Spec: networking.IngressSpec{
Backend: &defaultBackend,
DefaultBackend: &defaultBackend,
},
}
@@ -1545,26 +1571,26 @@ func TestValidateIngressUpdate(t *testing.T) {
},
"new Backend.Resource allowed on update": {
tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
oldIngress.Spec.Backend = &defaultBackend
newIngress.Spec.Backend = &networking.IngressBackend{
oldIngress.Spec.DefaultBackend = &defaultBackend
newIngress.Spec.DefaultBackend = &networking.IngressBackend{
Resource: resourceBackend}
},
expectedErrs: field.ErrorList{},
},
"old Backend.Resource allowed on update": {
"old DefaultBackend.Resource allowed on update": {
tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
oldIngress.Spec.Backend = &networking.IngressBackend{
oldIngress.Spec.DefaultBackend = &networking.IngressBackend{
Resource: resourceBackend}
newIngress.Spec.Backend = &networking.IngressBackend{
newIngress.Spec.DefaultBackend = &networking.IngressBackend{
Resource: resourceBackend}
},
expectedErrs: field.ErrorList{},
},
"changing spec.backend from resource -> no resource": {
tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
oldIngress.Spec.Backend = &networking.IngressBackend{
oldIngress.Spec.DefaultBackend = &networking.IngressBackend{
Resource: resourceBackend}
newIngress.Spec.Backend = &defaultBackend
newIngress.Spec.DefaultBackend = &defaultBackend
},
expectedErrs: field.ErrorList{},
},
@@ -1899,11 +1925,15 @@ func TestValidateIngressClassUpdate(t *testing.T) {
}
func TestValidateIngressTLS(t *testing.T) {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
serviceBackend := &networking.IngressServiceBackend{
Name: "defaultbackend",
Port: networking.ServiceBackendPort{
Number: 80,
},
}
defaultBackend := networking.IngressBackend{
Service: serviceBackend,
}
newValid := func() networking.Ingress {
return networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
@@ -1911,10 +1941,7 @@ func TestValidateIngressTLS(t *testing.T) {
Namespace: metav1.NamespaceDefault,
},
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
DefaultBackend: &defaultBackend,
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
@@ -1987,9 +2014,14 @@ func TestValidateIngressTLS(t *testing.T) {
}
func TestValidateIngressStatusUpdate(t *testing.T) {
serviceBackend := &networking.IngressServiceBackend{
Name: "defaultbackend",
Port: networking.ServiceBackendPort{
Number: 80,
},
}
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
Service: serviceBackend,
}
newValid := func() networking.Ingress {
@@ -2000,10 +2032,7 @@ func TestValidateIngressStatusUpdate(t *testing.T) {
ResourceVersion: "9",
},
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
DefaultBackend: &defaultBackend,
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",