bugfix: check Spec.AllocateLoadBalancerNodePorts for nodeport and skip zero usage in delta evaluator

Signed-off-by: pacoxu <paco.xu@daocloud.io>

When Spec.AllocateLoadBalancerNodePorts is "false" NodePort shall
not be included when computing quota for type:LoadBalancer.

Co-authored-by: uablrek
This commit is contained in:
pacoxu
2020-12-22 19:19:15 +08:00
parent 0f7c2dc314
commit 15867d9e8a
7 changed files with 240 additions and 17 deletions

View File

@@ -46,6 +46,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/features:go_default_library",
"//pkg/util/node:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
@@ -54,6 +55,9 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/util/clock:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/quota/v1:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/quota/v1/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)

View File

@@ -26,8 +26,10 @@ import (
"k8s.io/apiserver/pkg/admission"
quota "k8s.io/apiserver/pkg/quota/v1"
"k8s.io/apiserver/pkg/quota/v1/generic"
"k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core"
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/features"
)
// the name used for object count quota
@@ -128,14 +130,33 @@ func (p *serviceEvaluator) Usage(item runtime.Object) (corev1.ResourceList, erro
value := resource.NewQuantity(int64(ports), resource.DecimalSI)
result[corev1.ResourceServicesNodePorts] = *value
case corev1.ServiceTypeLoadBalancer:
// load balancer services need to count node ports and load balancers
value := resource.NewQuantity(int64(ports), resource.DecimalSI)
result[corev1.ResourceServicesNodePorts] = *value
// load balancer services need to count node ports. If creation of node ports
// is suppressed only ports with explicit NodePort values are counted.
// nodeports won't be allocated yet, so we can't simply count the actual values.
// We need to look at the intent.
if feature.DefaultFeatureGate.Enabled(features.ServiceLBNodePortControl) &&
svc.Spec.AllocateLoadBalancerNodePorts != nil &&
*svc.Spec.AllocateLoadBalancerNodePorts == false {
result[corev1.ResourceServicesNodePorts] = *portsWithNodePorts(svc)
} else {
value := resource.NewQuantity(int64(ports), resource.DecimalSI)
result[corev1.ResourceServicesNodePorts] = *value
}
result[corev1.ResourceServicesLoadBalancers] = *(resource.NewQuantity(1, resource.DecimalSI))
}
return result, nil
}
func portsWithNodePorts(svc *corev1.Service) *resource.Quantity {
count := 0
for _, p := range svc.Spec.Ports {
if p.NodePort != 0 {
count++
}
}
return resource.NewQuantity(int64(count), resource.DecimalSI)
}
// UsageStats calculates aggregate usage for the object.
func (p *serviceEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) {
return generic.CalculateUsageStats(options, p.listFuncByNamespace, generic.MatchesNoScopeFunc, p.Usage)

View File

@@ -24,7 +24,11 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
quota "k8s.io/apiserver/pkg/quota/v1"
"k8s.io/apiserver/pkg/quota/v1/generic"
"k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer"
)
func TestServiceEvaluatorMatchesResources(t *testing.T) {
@@ -52,8 +56,9 @@ func TestServiceEvaluatorMatchesResources(t *testing.T) {
func TestServiceEvaluatorUsage(t *testing.T) {
evaluator := NewServiceEvaluator(nil)
testCases := map[string]struct {
service *api.Service
usage corev1.ResourceList
service *api.Service
usage corev1.ResourceList
serviceLBNodePortControlEnabled bool
}{
"loadbalancer": {
service: &api.Service{
@@ -86,6 +91,27 @@ func TestServiceEvaluatorUsage(t *testing.T) {
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
},
},
"loadbalancer_2_ports": {
service: &api.Service{
Spec: api.ServiceSpec{
Type: api.ServiceTypeLoadBalancer,
Ports: []api.ServicePort{
{
Port: 27443,
},
{
Port: 27444,
},
},
},
},
usage: corev1.ResourceList{
corev1.ResourceServicesNodePorts: resource.MustParse("2"),
corev1.ResourceServicesLoadBalancers: resource.MustParse("1"),
corev1.ResourceServices: resource.MustParse("1"),
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
},
},
"clusterip": {
service: &api.Service{
Spec: api.ServiceSpec{
@@ -138,15 +164,113 @@ func TestServiceEvaluatorUsage(t *testing.T) {
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
},
},
"nodeports-disabled": {
service: &api.Service{
Spec: api.ServiceSpec{
Type: api.ServiceTypeLoadBalancer,
Ports: []api.ServicePort{
{
Port: 27443,
},
{
Port: 27444,
},
},
AllocateLoadBalancerNodePorts: utilpointer.BoolPtr(false),
},
},
usage: corev1.ResourceList{
corev1.ResourceServices: resource.MustParse("1"),
corev1.ResourceServicesNodePorts: resource.MustParse("0"),
corev1.ResourceServicesLoadBalancers: resource.MustParse("1"),
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
},
serviceLBNodePortControlEnabled: true,
},
"nodeports-default-enabled": {
service: &api.Service{
Spec: api.ServiceSpec{
Type: api.ServiceTypeLoadBalancer,
Ports: []api.ServicePort{
{
Port: 27443,
NodePort: 32001,
},
{
Port: 27444,
NodePort: 32002,
},
},
AllocateLoadBalancerNodePorts: nil,
},
},
usage: corev1.ResourceList{
corev1.ResourceServices: resource.MustParse("1"),
corev1.ResourceServicesNodePorts: resource.MustParse("2"),
corev1.ResourceServicesLoadBalancers: resource.MustParse("1"),
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
},
},
"nodeports-explicitly-enabled": {
service: &api.Service{
Spec: api.ServiceSpec{
Type: api.ServiceTypeLoadBalancer,
Ports: []api.ServicePort{
{
Port: 27443,
},
{
Port: 27444,
},
},
AllocateLoadBalancerNodePorts: utilpointer.BoolPtr(true),
},
},
usage: corev1.ResourceList{
corev1.ResourceServices: resource.MustParse("1"),
corev1.ResourceServicesNodePorts: resource.MustParse("2"),
corev1.ResourceServicesLoadBalancers: resource.MustParse("1"),
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
},
serviceLBNodePortControlEnabled: true,
},
"nodeports-disabled-but-specified": {
service: &api.Service{
Spec: api.ServiceSpec{
Type: api.ServiceTypeLoadBalancer,
Ports: []api.ServicePort{
{
Port: 27443,
NodePort: 32001,
},
{
Port: 27444,
NodePort: 32002,
},
},
AllocateLoadBalancerNodePorts: utilpointer.BoolPtr(false),
},
},
usage: corev1.ResourceList{
corev1.ResourceServices: resource.MustParse("1"),
corev1.ResourceServicesNodePorts: resource.MustParse("2"),
corev1.ResourceServicesLoadBalancers: resource.MustParse("1"),
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
},
serviceLBNodePortControlEnabled: true,
},
}
for testName, testCase := range testCases {
actual, err := evaluator.Usage(testCase.service)
if err != nil {
t.Errorf("%s unexpected error: %v", testName, err)
}
if !quota.Equals(testCase.usage, actual) {
t.Errorf("%s expected: %v, actual: %v", testName, testCase.usage, actual)
}
t.Run(testName, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceLBNodePortControl, testCase.serviceLBNodePortControlEnabled)()
actual, err := evaluator.Usage(testCase.service)
if err != nil {
t.Errorf("%s unexpected error: %v", testName, err)
}
if !quota.Equals(testCase.usage, actual) {
t.Errorf("%s expected: %v, actual: %v", testName, testCase.usage, actual)
}
})
}
}