mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 20:24:09 +00:00
Svc REST: Validate input before IP allocation
This commit started as removing FIXME comments, but in doing so I realized that the IP allocation process was using unvalidated user input. Before de-layering, validation was called twice - once before init and once after, which the init code depended on. Fortunately (or not?) we had duplicative checks that caught errors but with less friendly messages. This commit calls validation before initializing the rest of the IP-related fields. This also re-organizes that code a bit, cleans up error messages and comments, and adds a test SPECIFICALLY for the errors in those cases.
This commit is contained in:
parent
7602260d0a
commit
650f8cfd35
@ -4358,7 +4358,7 @@ func ValidateService(service *core.Service) field.ErrorList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dualstack <-> ClusterIPs <-> ipfamilies
|
// dualstack <-> ClusterIPs <-> ipfamilies
|
||||||
allErrs = append(allErrs, validateServiceClusterIPsRelatedFields(service)...)
|
allErrs = append(allErrs, ValidateServiceClusterIPsRelatedFields(service)...)
|
||||||
|
|
||||||
ipPath := specPath.Child("externalIPs")
|
ipPath := specPath.Child("externalIPs")
|
||||||
for i, ip := range service.Spec.ExternalIPs {
|
for i, ip := range service.Spec.ExternalIPs {
|
||||||
@ -6305,8 +6305,10 @@ func ValidateSpreadConstraintNotRepeat(fldPath *field.Path, constraint core.Topo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateServiceClusterIPsRelatedFields validates .spec.ClusterIPs,, .spec.IPFamilies, .spec.ipFamilyPolicy
|
// ValidateServiceClusterIPsRelatedFields validates .spec.ClusterIPs,,
|
||||||
func validateServiceClusterIPsRelatedFields(service *core.Service) field.ErrorList {
|
// .spec.IPFamilies, .spec.ipFamilyPolicy. This is exported because it is used
|
||||||
|
// during IP init and allocation.
|
||||||
|
func ValidateServiceClusterIPsRelatedFields(service *core.Service) field.ErrorList {
|
||||||
// ClusterIP, ClusterIPs, IPFamilyPolicy and IPFamilies are validated prior (all must be unset) for ExternalName service
|
// ClusterIP, ClusterIPs, IPFamilyPolicy and IPFamilies are validated prior (all must be unset) for ExternalName service
|
||||||
if service.Spec.Type == core.ServiceTypeExternalName {
|
if service.Spec.Type == core.ServiceTypeExternalName {
|
||||||
return field.ErrorList{}
|
return field.ErrorList{}
|
||||||
@ -6328,12 +6330,12 @@ func validateServiceClusterIPsRelatedFields(service *core.Service) field.ErrorLi
|
|||||||
if len(service.Spec.ClusterIPs) == 0 {
|
if len(service.Spec.ClusterIPs) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(clusterIPsField, ""))
|
allErrs = append(allErrs, field.Required(clusterIPsField, ""))
|
||||||
} else if service.Spec.ClusterIPs[0] != service.Spec.ClusterIP {
|
} else if service.Spec.ClusterIPs[0] != service.Spec.ClusterIP {
|
||||||
allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "element [0] must match clusterIP"))
|
allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "first value must match `clusterIP`"))
|
||||||
}
|
}
|
||||||
} else { // ClusterIP == ""
|
} else { // ClusterIP == ""
|
||||||
// If ClusterIP is not set, ClusterIPs must also be unset.
|
// If ClusterIP is not set, ClusterIPs must also be unset.
|
||||||
if len(service.Spec.ClusterIPs) != 0 {
|
if len(service.Spec.ClusterIPs) != 0 {
|
||||||
allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "must be empty when clusterIP is empty"))
|
allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "must be empty when `clusterIP` is not specified"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
@ -36,6 +35,7 @@ import (
|
|||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
apiservice "k8s.io/kubernetes/pkg/api/service"
|
apiservice "k8s.io/kubernetes/pkg/api/service"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
registry "k8s.io/kubernetes/pkg/registry/core/service"
|
registry "k8s.io/kubernetes/pkg/registry/core/service"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||||
@ -751,6 +751,68 @@ func isMatchingPreferDualStackClusterIPFields(oldService, service *api.Service)
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sameClusterIPs(lhs, rhs *api.Service) bool {
|
||||||
|
if len(rhs.Spec.ClusterIPs) != len(lhs.Spec.ClusterIPs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ip := range rhs.Spec.ClusterIPs {
|
||||||
|
if lhs.Spec.ClusterIPs[i] != ip {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func reducedClusterIPs(before, after *api.Service) bool {
|
||||||
|
if len(after.Spec.ClusterIPs) == 0 { // Not specified
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(after.Spec.ClusterIPs) < len(before.Spec.ClusterIPs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameIPFamilies(lhs, rhs *api.Service) bool {
|
||||||
|
if len(rhs.Spec.IPFamilies) != len(lhs.Spec.IPFamilies) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, family := range rhs.Spec.IPFamilies {
|
||||||
|
if lhs.Spec.IPFamilies[i] != family {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func reducedIPFamilies(before, after *api.Service) bool {
|
||||||
|
if len(after.Spec.IPFamilies) == 0 { // Not specified
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(after.Spec.IPFamilies) < len(before.Spec.IPFamilies)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get the IP family of a given IP.
|
||||||
|
func familyOf(ip string) api.IPFamily {
|
||||||
|
if netutils.IsIPv4String(ip) {
|
||||||
|
return api.IPv4Protocol
|
||||||
|
}
|
||||||
|
if netutils.IsIPv6String(ip) {
|
||||||
|
return api.IPv6Protocol
|
||||||
|
}
|
||||||
|
return api.IPFamily("unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to avoid nil-checks all over. Callers of this need to be checking
|
||||||
|
// for an exact value.
|
||||||
|
func getIPFamilyPolicy(svc *api.Service) api.IPFamilyPolicyType {
|
||||||
|
if svc.Spec.IPFamilyPolicy == nil {
|
||||||
|
return "" // callers need to handle this
|
||||||
|
}
|
||||||
|
return *svc.Spec.IPFamilyPolicy
|
||||||
|
}
|
||||||
|
|
||||||
// attempts to default service ip families according to cluster configuration
|
// attempts to default service ip families according to cluster configuration
|
||||||
// while ensuring that provided families are configured on cluster.
|
// while ensuring that provided families are configured on cluster.
|
||||||
func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) error {
|
func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) error {
|
||||||
@ -777,20 +839,63 @@ func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) e
|
|||||||
return nil // nothing more to do.
|
return nil // nothing more to do.
|
||||||
}
|
}
|
||||||
|
|
||||||
// two families or two IPs with SingleStack
|
// Update-only prep work.
|
||||||
if service.Spec.IPFamilyPolicy != nil {
|
if oldService != nil {
|
||||||
el := make(field.ErrorList, 0)
|
np := service.Spec.IPFamilyPolicy
|
||||||
if *(service.Spec.IPFamilyPolicy) == api.IPFamilyPolicySingleStack {
|
|
||||||
if len(service.Spec.ClusterIPs) == 2 {
|
// If they didn't specify policy, or specified anything but
|
||||||
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy, "must be RequireDualStack or PreferDualStack when multiple 'clusterIPs' are specified"))
|
// 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 len(service.Spec.IPFamilies) == 2 {
|
if reducedIPFamilies(oldService, service) {
|
||||||
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy, "must be RequireDualStack or PreferDualStack when multiple 'ipFamilies' are specified"))
|
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 {
|
||||||
|
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 len(el) > 0 {
|
// Do some loose pre-validation of the input. This makes it easier in the
|
||||||
return errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
// rest of allocation code to not have to consider corner cases.
|
||||||
|
// TODO(thockin): when we tighten validation (e.g. to require IPs) we will
|
||||||
|
// need a "strict" and a "loose" form of this.
|
||||||
|
if el := validation.ValidateServiceClusterIPsRelatedFields(service); len(el) != 0 {
|
||||||
|
return errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO(thockin): Move this logic to validation?
|
||||||
|
el := make(field.ErrorList, 0)
|
||||||
|
|
||||||
|
// Make sure ipFamilyPolicy makes sense for the provided ipFamilies and
|
||||||
|
// clusterIPs. Further checks happen below - after the special cases.
|
||||||
|
if getIPFamilyPolicy(service) == api.IPFamilyPolicySingleStack {
|
||||||
|
if len(service.Spec.ClusterIPs) == 2 {
|
||||||
|
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy,
|
||||||
|
"must be 'RequireDualStack' or 'PreferDualStack' when multiple cluster IPs are specified"))
|
||||||
|
}
|
||||||
|
if len(service.Spec.IPFamilies) == 2 {
|
||||||
|
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy,
|
||||||
|
"must be 'RequireDualStack' or 'PreferDualStack' when multiple IP families are specified"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -801,30 +906,30 @@ func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) e
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have previously validated for ip correctness and if family exist it will match ip family
|
// We previously validated that IPs are well-formed and that if an
|
||||||
// so the following is safe to do
|
// ipFamilies[] entry exists it matches the IP.
|
||||||
isIPv6 := netutils.IsIPv6String(ip)
|
fam := familyOf(ip)
|
||||||
|
|
||||||
// If the corresponding family is not specified, add it.
|
// If the corresponding family is not specified, add it.
|
||||||
if i >= len(service.Spec.IPFamilies) {
|
if i >= len(service.Spec.IPFamilies) {
|
||||||
if isIPv6 {
|
// Families are checked more later, but this is a better error in
|
||||||
// first make sure that family(ip) is configured
|
// this specific case (indicating the user-provided IP, rather
|
||||||
if _, found := al.serviceIPAllocatorsByFamily[api.IPv6Protocol]; !found {
|
// than than the auto-assigned family).
|
||||||
el := field.ErrorList{field.Invalid(field.NewPath("spec", "clusterIPs").Index(i), service.Spec.ClusterIPs, "may not use IPv6 on a cluster which is not configured for it")}
|
if _, found := al.serviceIPAllocatorsByFamily[fam]; !found {
|
||||||
return errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
el = append(el, field.Invalid(field.NewPath("spec", "clusterIPs").Index(i), service.Spec.ClusterIPs,
|
||||||
}
|
fmt.Sprintf("%s is not configured on this cluster", fam)))
|
||||||
service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv6Protocol)
|
|
||||||
} else {
|
} else {
|
||||||
// first make sure that family(ip) is configured
|
// OK to infer.
|
||||||
if _, found := al.serviceIPAllocatorsByFamily[api.IPv4Protocol]; !found {
|
service.Spec.IPFamilies = append(service.Spec.IPFamilies, fam)
|
||||||
el := field.ErrorList{field.Invalid(field.NewPath("spec", "clusterIPs").Index(i), service.Spec.ClusterIPs, "may not use IPv4 on a cluster which is not configured for it")}
|
|
||||||
return errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
|
||||||
}
|
|
||||||
service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv4Protocol)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have validation errors, bail out now so we don't make them worse.
|
||||||
|
if len(el) > 0 {
|
||||||
|
return errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
||||||
|
}
|
||||||
|
|
||||||
// Infer IPFamilyPolicy from IPFamilies[]. This block does not handle the
|
// Infer IPFamilyPolicy from IPFamilies[]. This block does not handle the
|
||||||
// final defaulting - that happens a bit later, after special cases.
|
// final defaulting - that happens a bit later, after special cases.
|
||||||
if service.Spec.IPFamilyPolicy == nil && len(service.Spec.IPFamilies) == 2 {
|
if service.Spec.IPFamilyPolicy == nil && len(service.Spec.IPFamilies) == 2 {
|
||||||
@ -870,19 +975,18 @@ func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) e
|
|||||||
// Everything below this MUST happen *after* the above special cases.
|
// Everything below this MUST happen *after* the above special cases.
|
||||||
//
|
//
|
||||||
|
|
||||||
// ipfamily check
|
|
||||||
// the following applies on all type of services including headless w/ selector
|
|
||||||
el := make(field.ErrorList, 0)
|
|
||||||
|
|
||||||
// Demanding dual-stack on a non dual-stack cluster.
|
// Demanding dual-stack on a non dual-stack cluster.
|
||||||
if service.Spec.IPFamilyPolicy != nil && *(service.Spec.IPFamilyPolicy) == api.IPFamilyPolicyRequireDualStack && len(al.serviceIPAllocatorsByFamily) < 2 {
|
if getIPFamilyPolicy(service) == api.IPFamilyPolicyRequireDualStack {
|
||||||
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy, "Cluster is not configured for dual stack services"))
|
if len(al.serviceIPAllocatorsByFamily) < 2 {
|
||||||
|
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy,
|
||||||
|
"this cluster is not configured for dual-stack services"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a family requested then it has to be configured on cluster.
|
// If there is a family requested then it has to be configured on cluster.
|
||||||
for i, ipFamily := range service.Spec.IPFamilies {
|
for i, ipFamily := range service.Spec.IPFamilies {
|
||||||
if _, found := al.serviceIPAllocatorsByFamily[ipFamily]; !found {
|
if _, found := al.serviceIPAllocatorsByFamily[ipFamily]; !found {
|
||||||
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilies").Index(i), service.Spec.ClusterIPs, fmt.Sprintf("ipfamily %v is not configured on cluster", ipFamily)))
|
el = append(el, field.Invalid(field.NewPath("spec", "ipFamilies").Index(i), ipFamily, "not configured on this cluster"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -911,9 +1015,7 @@ func (al *RESTAllocStuff) initIPFamilyFields(oldService, service *api.Service) e
|
|||||||
|
|
||||||
if service.Spec.IPFamilies[0] == api.IPv4Protocol {
|
if service.Spec.IPFamilies[0] == api.IPv4Protocol {
|
||||||
service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv6Protocol)
|
service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv6Protocol)
|
||||||
}
|
} else if service.Spec.IPFamilies[0] == api.IPv6Protocol {
|
||||||
|
|
||||||
if service.Spec.IPFamilies[0] == api.IPv6Protocol {
|
|
||||||
service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv4Protocol)
|
service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv4Protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
@ -376,16 +377,6 @@ func fmtIPFamilies(fams []api.IPFamily) string {
|
|||||||
return fmt.Sprintf("%v", fams)
|
return fmt.Sprintf("%v", fams)
|
||||||
}
|
}
|
||||||
|
|
||||||
func familyOf(ip string) api.IPFamily {
|
|
||||||
if netutils.IsIPv4String(ip) {
|
|
||||||
return api.IPv4Protocol
|
|
||||||
}
|
|
||||||
if netutils.IsIPv6String(ip) {
|
|
||||||
return api.IPv6Protocol
|
|
||||||
}
|
|
||||||
return api.IPFamily("unknown")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prove that create ignores IP and IPFamily stuff when type is ExternalName.
|
// Prove that create ignores IP and IPFamily stuff when type is ExternalName.
|
||||||
func TestCreateIgnoresIPsForExternalName(t *testing.T) {
|
func TestCreateIgnoresIPsForExternalName(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
@ -4765,6 +4756,141 @@ func TestCreateInitIPFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There are enough corner-cases that it's useful to have a test that asserts
|
||||||
|
// the errors. Some of these are in other tests, but this is clearer.
|
||||||
|
func TestCreateInvalidClusterIPInputs(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
families []api.IPFamily
|
||||||
|
svc *api.Service
|
||||||
|
expect []string
|
||||||
|
}{{
|
||||||
|
name: "bad_ipFamilyPolicy",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyType("garbage"))),
|
||||||
|
expect: []string{"Unsupported value"},
|
||||||
|
}, {
|
||||||
|
name: "requiredual_ipFamilyPolicy_on_singlestack",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
|
||||||
|
expect: []string{"cluster is not configured for dual-stack"},
|
||||||
|
}, {
|
||||||
|
name: "bad_ipFamilies_0_value",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilies(api.IPFamily("garbage"))),
|
||||||
|
expect: []string{"Unsupported value"},
|
||||||
|
}, {
|
||||||
|
name: "bad_ipFamilies_1_value",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilies(api.IPv4Protocol, api.IPFamily("garbage"))),
|
||||||
|
expect: []string{"Unsupported value"},
|
||||||
|
}, {
|
||||||
|
name: "bad_ipFamilies_2_value",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol, api.IPFamily("garbage"))),
|
||||||
|
expect: []string{"Unsupported value"},
|
||||||
|
}, {
|
||||||
|
name: "wrong_ipFamily",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilies(api.IPv6Protocol)),
|
||||||
|
expect: []string{"not configured on this cluster"},
|
||||||
|
}, {
|
||||||
|
name: "too_many_ipFamilies_on_singlestack",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol)),
|
||||||
|
expect: []string{"not configured on this cluster"},
|
||||||
|
}, {
|
||||||
|
name: "dup_ipFamily_singlestack",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv4Protocol)),
|
||||||
|
expect: []string{"Duplicate value"},
|
||||||
|
}, {
|
||||||
|
name: "dup_ipFamily_dualstack",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol, api.IPv6Protocol)),
|
||||||
|
expect: []string{"Duplicate value"},
|
||||||
|
}, {
|
||||||
|
name: "bad_IP",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetClusterIPs("garbage")),
|
||||||
|
expect: []string{"must be a valid IP"},
|
||||||
|
}, {
|
||||||
|
name: "IP_wrong_family",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetClusterIPs("2000::1")),
|
||||||
|
expect: []string{"not configured on this cluster"},
|
||||||
|
}, {
|
||||||
|
name: "IP_doesnt_match_family",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetIPFamilies(api.IPv4Protocol),
|
||||||
|
svctest.SetClusterIPs("2000::1")),
|
||||||
|
expect: []string{"expected an IPv4 value as indicated"},
|
||||||
|
}, {
|
||||||
|
name: "too_many_IPs_singlestack",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetClusterIPs("10.0.0.1", "10.0.0.2")),
|
||||||
|
expect: []string{"no more than one IP for each IP family"},
|
||||||
|
}, {
|
||||||
|
name: "too_many_IPs_dualstack",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetClusterIPs("10.0.0.1", "2000::1", "10.0.0.2")),
|
||||||
|
expect: []string{"only hold up to 2 values"},
|
||||||
|
}, {
|
||||||
|
name: "dup_IPs",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetClusterIPs("10.0.0.1", "10.0.0.1")),
|
||||||
|
expect: []string{"no more than one IP for each IP family"},
|
||||||
|
}, {
|
||||||
|
name: "empty_IP",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetClusterIPs("")),
|
||||||
|
expect: []string{"must be empty when", "must be a valid IP"},
|
||||||
|
}, {
|
||||||
|
name: "None_IP_1",
|
||||||
|
families: []api.IPFamily{api.IPv4Protocol},
|
||||||
|
svc: svctest.MakeService("foo",
|
||||||
|
svctest.SetClusterIPs("10.0.0.1", "None")),
|
||||||
|
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)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
|
||||||
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
|
_, err := storage.Create(ctx, tc.svc, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("unexpected success creating service")
|
||||||
|
}
|
||||||
|
t.Logf("INFO: errstr:\n %v", err)
|
||||||
|
for _, s := range tc.expect {
|
||||||
|
if !strings.Contains(err.Error(), s) {
|
||||||
|
t.Errorf("expected to find %q in the error:\n %s", s, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestCreateDeleteReuse(t *testing.T) {
|
func TestCreateDeleteReuse(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
Loading…
Reference in New Issue
Block a user