phase 2: api types + defaulting + validation + disabled fields handling

This commit is contained in:
Khaled Henidak(Kal) 2019-08-23 17:25:59 +00:00
parent d7ecc85239
commit 5e8ccda71c
12 changed files with 483 additions and 4 deletions

View File

@ -279,6 +279,11 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
types := []core.ServiceType{core.ServiceTypeClusterIP, core.ServiceTypeNodePort, core.ServiceTypeLoadBalancer}
*p = types[c.Rand.Intn(len(types))]
},
func(p *core.IPFamily, c fuzz.Continue) {
types := []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
selected := types[c.Rand.Intn(len(types))]
*p = selected
},
func(p *core.ServiceExternalTrafficPolicyType, c fuzz.Continue) {
types := []core.ServiceExternalTrafficPolicyType{core.ServiceExternalTrafficPolicyTypeCluster, core.ServiceExternalTrafficPolicyTypeLocal}
*p = types[c.Rand.Intn(len(types))]

View File

@ -3330,6 +3330,17 @@ type LoadBalancerIngress struct {
Hostname string
}
// IPFamily represents the IP Family (IPv4 or IPv6). This type is used
// to express the family of an IP expressed by a type (i.e. service.Spec.IPFamily)
type IPFamily string
const (
// IPv4Protocol indicates that this IP is IPv4 protocol
IPv4Protocol IPFamily = "IPv4"
// IPv6Protocol indicates that this IP is IPv6 protocol
IPv6Protocol IPFamily = "IPv6"
)
// ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct {
// Type determines how the Service is exposed. Defaults to ClusterIP. Valid
@ -3430,6 +3441,16 @@ type ServiceSpec struct {
// of peer discovery.
// +optional
PublishNotReadyAddresses bool
// ipFamily specifies whether this Service has a preference for a particular IP family (e.g. IPv4 vs.
// IPv6). If a specific IP family is requested, the clusterIP field will be allocated from that family, if it is
// available in the cluster. If no IP family is requested, the cluster's primary IP family will be used.
// Other IP fields (loadBalancerIP, loadBalancerSourceRanges, externalIPs) and controllers which
// allocate external load-balancers should use the same IP family. Endpoints for this Service will be of
// this family. This field is immutable after creation. Assigning a ServiceIPFamily not available in the
// cluster (e.g. IPv6 in IPv4 only cluster) is an error condition and will fail during clusterIP assignment.
// +optional
IPFamily *IPFamily
}
type ServicePort struct {

View File

@ -24,6 +24,10 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/parsers"
utilpointer "k8s.io/utils/pointer"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
utilnet "k8s.io/utils/net"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
@ -128,6 +132,33 @@ func SetDefaults_Service(obj *v1.Service) {
obj.Spec.ExternalTrafficPolicy == "" {
obj.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeCluster
}
// if dualstack feature gate is on then we need to default
// Spec.IPFamily correctly. This is to cover the case
// when an existing cluster have been converted to dualstack
// i.e. it already contain services with Spec.IPFamily==nil
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) &&
obj.Spec.Type != v1.ServiceTypeExternalName &&
obj.Spec.ClusterIP != "" && /*has an ip already set*/
obj.Spec.ClusterIP != "None" && /* not converting from ExternalName to other */
obj.Spec.IPFamily == nil /* family was not previously set */ {
// there is a change that the ClusterIP (set by user) is unparsable.
// in this case, the family will be set mistakenly to ipv4 (because
// the util function does not parse errors *sigh*). The error
// will be caught in validation which asserts the validity of the
// IP and the service object will not be persisted with the wrong IP
// family
ipv6 := v1.IPv6Protocol
ipv4 := v1.IPv4Protocol
if utilnet.IsIPv6String(obj.Spec.ClusterIP) {
obj.Spec.IPFamily = &ipv6
} else {
obj.Spec.IPFamily = &ipv4
}
}
}
func SetDefaults_Pod(obj *v1.Pod) {
// If limits are specified, but requests are not, default requests to limits

View File

@ -35,6 +35,10 @@ import (
// enforce that all types are installed
_ "k8s.io/kubernetes/pkg/api/testapi"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
)
// TestWorkloadDefaults detects changes to defaults within PodTemplateSpec.
@ -976,6 +980,140 @@ func TestSetDefaultService(t *testing.T) {
}
}
func TestSetDefaultServiceIPFamily(t *testing.T) {
svc := v1.Service{
Spec: v1.ServiceSpec{
SessionAffinity: v1.ServiceAffinityNone,
Type: v1.ServiceTypeClusterIP,
},
}
testCases := []struct {
name string
inSvcTweak func(s v1.Service) v1.Service
outSvcTweak func(s v1.Service) v1.Service
enableDualStack bool
}{
{
name: "dualstack off. ipfamily not set",
inSvcTweak: func(s v1.Service) v1.Service { return s },
outSvcTweak: func(s v1.Service) v1.Service { return s },
enableDualStack: false,
},
{
name: "dualstack on. ipfamily not set, service is *not* ClusterIP-able",
inSvcTweak: func(s v1.Service) v1.Service {
s.Spec.Type = v1.ServiceTypeExternalName
return s
},
outSvcTweak: func(s v1.Service) v1.Service { return s },
enableDualStack: true,
},
{
name: "dualstack off. ipfamily set",
inSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
return s
},
enableDualStack: false,
},
{
name: "dualstack off. ipfamily not set. clusterip set",
inSvcTweak: func(s v1.Service) v1.Service {
s.Spec.ClusterIP = "1.1.1.1"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
return s
},
enableDualStack: false,
},
{
name: "dualstack on. ipfamily not set (clusterIP is v4)",
inSvcTweak: func(s v1.Service) v1.Service {
s.Spec.ClusterIP = "1.1.1.1"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
return s
},
enableDualStack: true,
},
{
name: "dualstack on. ipfamily not set (clusterIP is v6)",
inSvcTweak: func(s v1.Service) v1.Service {
s.Spec.ClusterIP = "fdd7:7713:8917:77ed:ffff:ffff:ffff:ffff"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv6Service := v1.IPv6Protocol
s.Spec.IPFamily = &ipv6Service
return s
},
enableDualStack: true,
},
{
name: "dualstack on. ipfamily set (clusterIP is v4)",
inSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
s.Spec.ClusterIP = "1.1.1.1"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv4Service := v1.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
return s
},
enableDualStack: true,
},
{
name: "dualstack on. ipfamily set (clusterIP is v6)",
inSvcTweak: func(s v1.Service) v1.Service {
ipv6Service := v1.IPv6Protocol
s.Spec.IPFamily = &ipv6Service
s.Spec.ClusterIP = "fdd7:7713:8917:77ed:ffff:ffff:ffff:ffff"
return s
},
outSvcTweak: func(s v1.Service) v1.Service {
ipv6Service := v1.IPv6Protocol
s.Spec.IPFamily = &ipv6Service
return s
},
enableDualStack: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
tweakedIn := tc.inSvcTweak(svc)
expectedSvc := tc.outSvcTweak(svc)
defaulted := roundTrip(t, runtime.Object(&tweakedIn))
defaultedSvc := defaulted.(*v1.Service)
if expectedSvc.Spec.IPFamily != nil {
if defaultedSvc.Spec.IPFamily == nil {
t.Fatalf("defaulted service ipfamily is nil while expected is not")
}
if *(expectedSvc.Spec.IPFamily) != *(defaultedSvc.Spec.IPFamily) {
t.Fatalf("defaulted service ipfamily %v does not match expected %v", defaultedSvc.Spec.IPFamily, expectedSvc.Spec.IPFamily)
}
}
if expectedSvc.Spec.IPFamily == nil && defaultedSvc.Spec.IPFamily != nil {
t.Fatalf("defaulted service ipfamily is not nil, while expected service ipfamily is")
}
})
}
}
func TestSetDefaultServiceSessionAffinityConfig(t *testing.T) {
testCases := map[string]v1.Service{
"SessionAffinityConfig is empty": {

View File

@ -3893,6 +3893,7 @@ func ValidatePodTemplateUpdate(newPod, oldPod *core.PodTemplate) field.ErrorList
var supportedSessionAffinityType = sets.NewString(string(core.ServiceAffinityClientIP), string(core.ServiceAffinityNone))
var supportedServiceType = sets.NewString(string(core.ServiceTypeClusterIP), string(core.ServiceTypeNodePort),
string(core.ServiceTypeLoadBalancer), string(core.ServiceTypeExternalName))
var supportedServiceIPFamily = sets.NewString(string(core.IPv4Protocol), string(core.IPv6Protocol))
// ValidateService tests if required fields/annotations of a Service are valid.
func ValidateService(service *core.Service) field.ErrorList {
@ -4064,8 +4065,22 @@ func ValidateService(service *core.Service) field.ErrorList {
}
}
allErrs = append(allErrs, validateServiceExternalTrafficFieldsValue(service)...)
//if an ipfamily provided then it has to be one of the supported values
// note:
// - we don't validate service.Spec.IPFamily is supported by the cluster
// - we don't validate service.Spec.ClusterIP is within a range supported by the cluster
// both of these validations are done by the ipallocator
// if the gate is on this field is required (and defaulted by REST if not provided by user)
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && service.Spec.IPFamily == nil {
allErrs = append(allErrs, field.Required(specPath.Child("ipFamily"), ""))
}
if service.Spec.IPFamily != nil && !supportedServiceIPFamily.Has(string(*service.Spec.IPFamily)) {
allErrs = append(allErrs, field.NotSupported(specPath.Child("ipFamily"), service.Spec.IPFamily, supportedServiceIPFamily.List()))
}
allErrs = append(allErrs, validateServiceExternalTrafficFieldsValue(service)...)
return allErrs
}
@ -4154,12 +4169,19 @@ func ValidateServiceExternalTrafficFieldsCombination(service *core.Service) fiel
func ValidateServiceUpdate(service, oldService *core.Service) field.ErrorList {
allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata"))
// ClusterIP should be immutable for services using it (every type other than ExternalName)
// ClusterIP and IPFamily should be immutable for services using it (every type other than ExternalName)
// which do not have ClusterIP assigned yet (empty string value)
if service.Spec.Type != core.ServiceTypeExternalName {
if oldService.Spec.Type != core.ServiceTypeExternalName && oldService.Spec.ClusterIP != "" {
allErrs = append(allErrs, ValidateImmutableField(service.Spec.ClusterIP, oldService.Spec.ClusterIP, field.NewPath("spec", "clusterIP"))...)
}
// notes:
// we drop the IPFamily field when the Dualstack gate is off.
// once the gate is on, we start assigning default ipfamily according to cluster settings. in other words
// though the field is immutable, we allow (onetime) change from nil==> to value
if oldService.Spec.IPFamily != nil {
allErrs = append(allErrs, ValidateImmutableField(service.Spec.IPFamily, oldService.Spec.IPFamily, field.NewPath("spec", "ipFamily"))...)
}
}
allErrs = append(allErrs, ValidateService(service)...)

View File

@ -9134,6 +9134,7 @@ func TestValidatePodStatusUpdate(t *testing.T) {
}
func makeValidService() core.Service {
serviceIPFamily := core.IPv4Protocol
return core.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "valid",
@ -9147,6 +9148,7 @@ func makeValidService() core.Service {
SessionAffinity: "None",
Type: core.ServiceTypeClusterIP,
Ports: []core.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt(8675)}},
IPFamily: &serviceIPFamily,
},
}
}
@ -10072,6 +10074,29 @@ func TestValidateService(t *testing.T) {
},
numErrs: 1,
},
{
name: "valid, nil service IPFamily",
tweakSvc: func(s *core.Service) {
s.Spec.IPFamily = nil
},
numErrs: 0,
},
{
name: "valid, service with valid IPFamily",
tweakSvc: func(s *core.Service) {
ipv4Service := core.IPv4Protocol
s.Spec.IPFamily = &ipv4Service
},
numErrs: 0,
},
{
name: "invalid, service with invalid IPFamily",
tweakSvc: func(s *core.Service) {
invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
s.Spec.IPFamily = &invalidServiceIPFamily
},
numErrs: 1,
},
}
for _, tc := range testCases {
@ -11922,6 +11947,80 @@ func TestValidateServiceUpdate(t *testing.T) {
},
numErrs: 1,
},
/* Service IP Family */
{
name: "same ServiceIPFamily",
tweakSvc: func(oldSvc, newSvc *core.Service) {
ipv4Service := core.IPv4Protocol
oldSvc.Spec.Type = core.ServiceTypeClusterIP
oldSvc.Spec.IPFamily = &ipv4Service
newSvc.Spec.Type = core.ServiceTypeClusterIP
newSvc.Spec.IPFamily = &ipv4Service
},
numErrs: 0,
},
{
name: "ExternalName while changing Service IPFamily",
tweakSvc: func(oldSvc, newSvc *core.Service) {
ipv4Service := core.IPv4Protocol
oldSvc.Spec.ExternalName = "somename"
oldSvc.Spec.Type = core.ServiceTypeExternalName
oldSvc.Spec.IPFamily = &ipv4Service
ipv6Service := core.IPv6Protocol
newSvc.Spec.ExternalName = "somename"
newSvc.Spec.Type = core.ServiceTypeExternalName
newSvc.Spec.IPFamily = &ipv6Service
},
numErrs: 0,
},
{
name: "setting ipfamily from nil to v4",
tweakSvc: func(oldSvc, newSvc *core.Service) {
oldSvc.Spec.IPFamily = nil
ipv4Service := core.IPv4Protocol
newSvc.Spec.ExternalName = "somename"
newSvc.Spec.IPFamily = &ipv4Service
},
numErrs: 0,
},
{
name: "setting ipfamily from nil to v6",
tweakSvc: func(oldSvc, newSvc *core.Service) {
oldSvc.Spec.IPFamily = nil
ipv6Service := core.IPv6Protocol
newSvc.Spec.ExternalName = "somename"
newSvc.Spec.IPFamily = &ipv6Service
},
numErrs: 0,
},
{
name: "remove ipfamily",
tweakSvc: func(oldSvc, newSvc *core.Service) {
ipv6Service := core.IPv6Protocol
oldSvc.Spec.IPFamily = &ipv6Service
newSvc.Spec.IPFamily = nil
},
numErrs: 1,
},
{
name: "change ServiceIPFamily",
tweakSvc: func(oldSvc, newSvc *core.Service) {
ipv4Service := core.IPv4Protocol
oldSvc.Spec.Type = core.ServiceTypeClusterIP
oldSvc.Spec.IPFamily = &ipv4Service
ipv6Service := core.IPv6Protocol
newSvc.Spec.Type = core.ServiceTypeClusterIP
newSvc.Spec.IPFamily = &ipv6Service
},
numErrs: 1,
},
}
for _, tc := range testCases {

View File

@ -26,6 +26,9 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
)
// svcStrategy implements behavior for Services
@ -114,6 +117,21 @@ func (svcStrategy) Export(ctx context.Context, obj runtime.Object, exact bool) e
// newSvc.Spec.MyFeature = nil
// }
func dropServiceDisabledFields(newSvc *api.Service, oldSvc *api.Service) {
// Drop IPFamily if DualStack is not enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && !serviceIPFamilyInUse(oldSvc) {
newSvc.Spec.IPFamily = nil
}
}
// returns true if svc.Spec.ServiceIPFamily field is in use
func serviceIPFamilyInUse(svc *api.Service) bool {
if svc == nil {
return false
}
if svc.Spec.IPFamily != nil {
return true
}
return false
}
type serviceStatusStrategy struct {

View File

@ -23,11 +23,16 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
api "k8s.io/kubernetes/pkg/apis/core"
_ "k8s.io/kubernetes/pkg/apis/core/install"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
)
func TestExportService(t *testing.T) {
@ -128,6 +133,7 @@ func TestCheckGeneratedNameError(t *testing.T) {
}
func makeValidService() api.Service {
defaultServiceIPFamily := api.IPv4Protocol
return api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "valid",
@ -141,6 +147,7 @@ func makeValidService() api.Service {
SessionAffinity: "None",
Type: api.ServiceTypeClusterIP,
Ports: []api.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt(8675)}},
IPFamily: &defaultServiceIPFamily,
},
}
}
@ -241,3 +248,70 @@ func TestServiceStatusStrategy(t *testing.T) {
t.Errorf("Unexpected error %v", errs)
}
}
func makeServiceWithIPFamily(IPFamily *api.IPFamily) *api.Service {
return &api.Service{
Spec: api.ServiceSpec{
IPFamily: IPFamily,
},
}
}
func TestDropDisabledField(t *testing.T) {
ipv4Service := api.IPv4Protocol
ipv6Service := api.IPv6Protocol
testCases := []struct {
name string
enableDualStack bool
svc *api.Service
oldSvc *api.Service
compareSvc *api.Service
}{
{
name: "not dual stack, field not used",
enableDualStack: false,
svc: makeServiceWithIPFamily(nil),
oldSvc: nil,
compareSvc: makeServiceWithIPFamily(nil),
},
{
name: "not dual stack, field used in new, not in old",
enableDualStack: false,
svc: makeServiceWithIPFamily(&ipv4Service),
oldSvc: nil,
compareSvc: makeServiceWithIPFamily(nil),
},
{
name: "not dual stack, field used in old and new",
enableDualStack: false,
svc: makeServiceWithIPFamily(&ipv4Service),
oldSvc: makeServiceWithIPFamily(&ipv4Service),
compareSvc: makeServiceWithIPFamily(&ipv4Service),
},
{
name: "dualstack, field used",
enableDualStack: true,
svc: makeServiceWithIPFamily(&ipv6Service),
oldSvc: nil,
compareSvc: makeServiceWithIPFamily(&ipv6Service),
},
/* add more tests for other dropped fields as needed */
}
for _, tc := range testCases {
func() {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
old := tc.oldSvc.DeepCopy()
dropServiceDisabledFields(tc.svc, tc.oldSvc)
// old node should never be changed
if !reflect.DeepEqual(tc.oldSvc, old) {
t.Errorf("%v: old svc changed: %v", tc.name, diff.ObjectReflectDiff(tc.oldSvc, old))
}
if !reflect.DeepEqual(tc.svc, tc.compareSvc) {
t.Errorf("%v: unexpected svc spec: %v", tc.name, diff.ObjectReflectDiff(tc.svc, tc.compareSvc))
}
}()
}
}

View File

@ -9,6 +9,7 @@
- k8s.io/kubernetes/pkg/util
- k8s.io/api/core/v1
- k8s.io/utils/pointer
- k8s.io/utils/net
- k8s.io/klog
# the following are temporary and should go away. Think twice (or more) before adding anything here.

View File

@ -3794,6 +3794,17 @@ type LoadBalancerIngress struct {
Hostname string `json:"hostname,omitempty" protobuf:"bytes,2,opt,name=hostname"`
}
// IPFamily represents the IP Family (IPv4 or IPv6). This type is used
// to express the family of an IP expressed by a type (i.e. service.Spec.IPFamily)
type IPFamily string
const (
// IPv4Protocol indicates that this IP is IPv4 protocol
IPv4Protocol IPFamily = "IPv4"
// IPv6Protocol indicates that this IP is IPv6 protocol
IPv6Protocol IPFamily = "IPv6"
)
// ServiceSpec describes the attributes that a user creates on a service.
type ServiceSpec struct {
// The list of ports that are exposed by this service.
@ -3909,6 +3920,16 @@ type ServiceSpec struct {
// sessionAffinityConfig contains the configurations of session affinity.
// +optional
SessionAffinityConfig *SessionAffinityConfig `json:"sessionAffinityConfig,omitempty" protobuf:"bytes,14,opt,name=sessionAffinityConfig"`
// ipFamily specifies whether this Service has a preference for a particular IP family (e.g. IPv4 vs.
// IPv6). If a specific IP family is requested, the clusterIP field will be allocated from that family, if it is
// available in the cluster. If no IP family is requested, the cluster's primary IP family will be used.
// Other IP fields (loadBalancerIP, loadBalancerSourceRanges, externalIPs) and controllers which
// allocate external load-balancers should use the same IP family. Endpoints for this Service will be of
// this family. This field is immutable after creation. Assigning a ServiceIPFamily not available in the
// cluster (e.g. IPv6 in IPv4 only cluster) is an error condition and will fail during clusterIP assignment.
// +optional
IPFamily *IPFamily `json:"ipFamily,omitempty" protobuf:"bytes,15,opt,name=ipFamily,Configcasttype=IPFamily"`
}
// ServicePort contains information on service's port.

View File

@ -2478,6 +2478,11 @@ func describeService(service *corev1.Service, endpoints *corev1.Endpoints, event
w.Write(LEVEL_0, "Selector:\t%s\n", labels.FormatLabels(service.Spec.Selector))
w.Write(LEVEL_0, "Type:\t%s\n", service.Spec.Type)
w.Write(LEVEL_0, "IP:\t%s\n", service.Spec.ClusterIP)
if service.Spec.IPFamily != nil {
w.Write(LEVEL_0, "IPFamily:\t%s\n", *(service.Spec.IPFamily))
}
if len(service.Spec.ExternalIPs) > 0 {
w.Write(LEVEL_0, "External IPs:\t%v\n", strings.Join(service.Spec.ExternalIPs, ","))
}

View File

@ -351,6 +351,8 @@ func getResourceList(cpu, memory string) corev1.ResourceList {
}
func TestDescribeService(t *testing.T) {
defaultServiceIPFamily := corev1.IPv4Protocol
testCases := []struct {
name string
service *corev1.Service
@ -364,7 +366,8 @@ func TestDescribeService(t *testing.T) {
Namespace: "foo",
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Type: corev1.ServiceTypeLoadBalancer,
IPFamily: &defaultServiceIPFamily,
Ports: []corev1.ServicePort{{
Name: "port-tcp",
Port: 8080,
@ -402,7 +405,8 @@ func TestDescribeService(t *testing.T) {
Namespace: "foo",
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Type: corev1.ServiceTypeLoadBalancer,
IPFamily: &defaultServiceIPFamily,
Ports: []corev1.ServicePort{{
Name: "port-tcp",
Port: 8080,
@ -432,6 +436,46 @@ func TestDescribeService(t *testing.T) {
"HealthCheck NodePort", "32222",
},
},
{
name: "test-ServiceIPFamily",
service: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
IPFamily: &defaultServiceIPFamily,
Ports: []corev1.ServicePort{{
Name: "port-tcp",
Port: 8080,
Protocol: corev1.ProtocolTCP,
TargetPort: intstr.FromString("targetPort"),
NodePort: 31111,
}},
Selector: map[string]string{"blah": "heh"},
ClusterIP: "1.2.3.4",
LoadBalancerIP: "5.6.7.8",
SessionAffinity: "None",
ExternalTrafficPolicy: "Local",
HealthCheckNodePort: 32222,
},
},
expect: []string{
"Name", "bar",
"Namespace", "foo",
"Selector", "blah=heh",
"Type", "LoadBalancer",
"IP", "1.2.3.4",
"IPFamily", "IPv4",
"Port", "port-tcp", "8080/TCP",
"TargetPort", "targetPort/TCP",
"NodePort", "port-tcp", "31111/TCP",
"Session Affinity", "None",
"External Traffic Policy", "Local",
"HealthCheck NodePort", "32222",
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {