2015-02-24 16:17:41 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2015 The Kubernetes Authors .
2015-02-24 16:17:41 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2015-10-10 03:58:57 +00:00
package resourcequota
2015-02-24 16:17:41 +00:00
import (
2017-10-27 15:07:53 +00:00
"fmt"
2019-03-14 01:56:39 +00:00
"net/http"
"net/http/httptest"
2016-02-22 16:15:09 +00:00
"strings"
2019-03-14 01:56:39 +00:00
"sync"
2015-02-24 16:17:41 +00:00
"testing"
2019-03-14 01:56:39 +00:00
"time"
2015-02-24 16:17:41 +00:00
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
2017-01-25 13:13:07 +00:00
"k8s.io/apimachinery/pkg/api/resource"
2017-01-17 03:38:19 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-03-14 03:13:51 +00:00
"k8s.io/apimachinery/pkg/labels"
2017-10-27 15:07:53 +00:00
"k8s.io/apimachinery/pkg/runtime"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
2017-06-23 20:56:37 +00:00
"k8s.io/client-go/informers"
2017-10-27 15:07:53 +00:00
"k8s.io/client-go/kubernetes"
2017-06-23 20:56:37 +00:00
"k8s.io/client-go/kubernetes/fake"
2019-03-14 01:56:39 +00:00
"k8s.io/client-go/rest"
2017-01-25 20:07:10 +00:00
core "k8s.io/client-go/testing"
2017-10-27 15:07:53 +00:00
"k8s.io/client-go/tools/cache"
2015-11-11 21:19:39 +00:00
"k8s.io/kubernetes/pkg/controller"
2018-08-27 13:49:26 +00:00
quota "k8s.io/kubernetes/pkg/quota/v1"
"k8s.io/kubernetes/pkg/quota/v1/generic"
"k8s.io/kubernetes/pkg/quota/v1/install"
2015-02-24 16:17:41 +00:00
)
2016-11-18 20:50:17 +00:00
func getResourceList ( cpu , memory string ) v1 . ResourceList {
res := v1 . ResourceList { }
2015-02-24 16:17:41 +00:00
if cpu != "" {
2016-11-18 20:50:17 +00:00
res [ v1 . ResourceCPU ] = resource . MustParse ( cpu )
2015-02-24 16:17:41 +00:00
}
if memory != "" {
2016-11-18 20:50:17 +00:00
res [ v1 . ResourceMemory ] = resource . MustParse ( memory )
2015-02-24 16:17:41 +00:00
}
2015-08-13 14:19:27 +00:00
return res
}
2015-02-24 16:17:41 +00:00
2016-11-18 20:50:17 +00:00
func getResourceRequirements ( requests , limits v1 . ResourceList ) v1 . ResourceRequirements {
res := v1 . ResourceRequirements { }
2015-08-13 14:19:27 +00:00
res . Requests = requests
res . Limits = limits
2015-02-24 16:17:41 +00:00
return res
}
2017-10-27 15:07:53 +00:00
func mockDiscoveryFunc ( ) ( [ ] * metav1 . APIResourceList , error ) {
return [ ] * metav1 . APIResourceList { } , nil
}
func mockListerForResourceFunc ( listersForResource map [ schema . GroupVersionResource ] cache . GenericLister ) quota . ListerForResourceFunc {
return func ( gvr schema . GroupVersionResource ) ( cache . GenericLister , error ) {
lister , found := listersForResource [ gvr ]
if ! found {
return nil , fmt . Errorf ( "no lister found for resource" )
}
return lister , nil
2015-02-24 16:17:41 +00:00
}
2017-10-27 15:07:53 +00:00
}
func newGenericLister ( groupResource schema . GroupResource , items [ ] runtime . Object ) cache . GenericLister {
store := cache . NewIndexer ( cache . MetaNamespaceKeyFunc , cache . Indexers { "namespace" : cache . MetaNamespaceIndexFunc } )
for _ , item := range items {
store . Add ( item )
2015-02-24 16:17:41 +00:00
}
2017-10-27 15:07:53 +00:00
return cache . NewGenericLister ( store , groupResource )
}
2019-03-14 03:13:51 +00:00
func newErrorLister ( ) cache . GenericLister {
return errorLister { }
}
type errorLister struct {
}
func ( errorLister ) List ( selector labels . Selector ) ( ret [ ] runtime . Object , err error ) {
return nil , fmt . Errorf ( "error listing" )
}
func ( errorLister ) Get ( name string ) ( runtime . Object , error ) {
return nil , fmt . Errorf ( "error getting" )
}
func ( errorLister ) ByNamespace ( namespace string ) cache . GenericNamespaceLister {
return errorLister { }
}
2017-10-27 15:07:53 +00:00
type quotaController struct {
* ResourceQuotaController
stop chan struct { }
}
2015-02-24 16:17:41 +00:00
2019-03-14 01:56:39 +00:00
func setupQuotaController ( t * testing . T , kubeClient kubernetes . Interface , lister quota . ListerForResourceFunc , discoveryFunc NamespacedResourcesFunc ) quotaController {
2017-02-10 16:25:54 +00:00
informerFactory := informers . NewSharedInformerFactory ( kubeClient , controller . NoResyncPeriodFunc ( ) )
2017-10-27 15:07:53 +00:00
quotaConfiguration := install . NewQuotaConfigurationForControllers ( lister )
alwaysStarted := make ( chan struct { } )
close ( alwaysStarted )
2016-02-22 16:15:09 +00:00
resourceQuotaControllerOptions := & ResourceQuotaControllerOptions {
2017-11-12 11:00:21 +00:00
QuotaClient : kubeClient . CoreV1 ( ) ,
2017-10-27 15:07:53 +00:00
ResourceQuotaInformer : informerFactory . Core ( ) . V1 ( ) . ResourceQuotas ( ) ,
ResyncPeriod : controller . NoResyncPeriodFunc ,
2016-03-07 07:20:32 +00:00
ReplenishmentResyncPeriod : controller . NoResyncPeriodFunc ,
2017-10-27 15:07:53 +00:00
IgnoredResourcesFunc : quotaConfiguration . IgnoredResources ,
2019-03-14 01:56:39 +00:00
DiscoveryFunc : discoveryFunc ,
2017-10-27 15:07:53 +00:00
Registry : generic . NewRegistry ( quotaConfiguration . Evaluators ( ) ) ,
InformersStarted : alwaysStarted ,
2019-03-14 01:56:39 +00:00
InformerFactory : informerFactory ,
2016-02-22 16:15:09 +00:00
}
2017-10-27 15:07:53 +00:00
qc , err := NewResourceQuotaController ( resourceQuotaControllerOptions )
2015-02-24 16:17:41 +00:00
if err != nil {
2017-10-27 15:07:53 +00:00
t . Fatal ( err )
2015-02-24 16:17:41 +00:00
}
2017-10-27 15:07:53 +00:00
stop := make ( chan struct { } )
go informerFactory . Start ( stop )
return quotaController { qc , stop }
2015-02-24 16:17:41 +00:00
}
2015-04-16 21:46:27 +00:00
2017-10-27 15:07:53 +00:00
func newTestPods ( ) [ ] runtime . Object {
return [ ] runtime . Object {
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-running" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodRunning } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "100m" , "1Gi" ) , getResourceList ( "" , "" ) ) } } ,
2015-04-16 21:46:27 +00:00
} ,
} ,
2017-10-27 15:07:53 +00:00
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-running-2" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodRunning } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "100m" , "1Gi" ) , getResourceList ( "" , "" ) ) } } ,
2015-04-16 21:46:27 +00:00
} ,
} ,
2017-10-27 15:07:53 +00:00
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-failed" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodFailed } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "100m" , "1Gi" ) , getResourceList ( "" , "" ) ) } } ,
2015-04-16 21:46:27 +00:00
} ,
2016-02-22 16:15:09 +00:00
} ,
}
2015-04-16 21:46:27 +00:00
}
2017-10-27 15:07:53 +00:00
2018-07-18 19:26:34 +00:00
func newBestEffortTestPods ( ) [ ] runtime . Object {
return [ ] runtime . Object {
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-running" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodRunning } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "" , "" ) , getResourceList ( "" , "" ) ) } } ,
} ,
} ,
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-running-2" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodRunning } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "" , "" ) , getResourceList ( "" , "" ) ) } } ,
} ,
} ,
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-failed" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodFailed } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "100m" , "1Gi" ) , getResourceList ( "" , "" ) ) } } ,
} ,
} ,
}
}
func newTestPodsWithPriorityClasses ( ) [ ] runtime . Object {
return [ ] runtime . Object {
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-running" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodRunning } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "500m" , "50Gi" ) , getResourceList ( "" , "" ) ) } } ,
PriorityClassName : "high" ,
} ,
} ,
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-running-2" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodRunning } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "100m" , "1Gi" ) , getResourceList ( "" , "" ) ) } } ,
PriorityClassName : "low" ,
} ,
} ,
& v1 . Pod {
ObjectMeta : metav1 . ObjectMeta { Name : "pod-failed" , Namespace : "testing" } ,
Status : v1 . PodStatus { Phase : v1 . PodFailed } ,
Spec : v1 . PodSpec {
Volumes : [ ] v1 . Volume { { Name : "vol" } } ,
Containers : [ ] v1 . Container { { Name : "ctr" , Image : "image" , Resources : getResourceRequirements ( getResourceList ( "100m" , "1Gi" ) , getResourceList ( "" , "" ) ) } } ,
} ,
} ,
}
}
2017-10-27 15:07:53 +00:00
func TestSyncResourceQuota ( t * testing . T ) {
testCases := map [ string ] struct {
gvr schema . GroupVersionResource
2019-03-14 03:13:51 +00:00
errorGVR schema . GroupVersionResource
2017-10-27 15:07:53 +00:00
items [ ] runtime . Object
quota v1 . ResourceQuota
status v1 . ResourceQuotaStatus
2019-03-14 03:13:51 +00:00
expectedError string
2017-10-27 15:07:53 +00:00
expectedActionSet sets . String
} {
2018-07-18 19:26:34 +00:00
"non-matching-best-effort-scoped-quota" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Scopes : [ ] v1 . ResourceQuotaScope { v1 . ResourceQuotaScopeBestEffort } ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
v1 . ResourceMemory : resource . MustParse ( "0" ) ,
v1 . ResourcePods : resource . MustParse ( "0" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPods ( ) ,
} ,
"matching-best-effort-scoped-quota" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Scopes : [ ] v1 . ResourceQuotaScope { v1 . ResourceQuotaScopeBestEffort } ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
v1 . ResourceMemory : resource . MustParse ( "0" ) ,
v1 . ResourcePods : resource . MustParse ( "2" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newBestEffortTestPods ( ) ,
} ,
"non-matching-priorityclass-scoped-quota-OpExists" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpExists } ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
v1 . ResourceMemory : resource . MustParse ( "0" ) ,
v1 . ResourcePods : resource . MustParse ( "0" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPods ( ) ,
} ,
"matching-priorityclass-scoped-quota-OpExists" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpExists } ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "600m" ) ,
v1 . ResourceMemory : resource . MustParse ( "51Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "2" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPodsWithPriorityClasses ( ) ,
} ,
"matching-priorityclass-scoped-quota-OpIn" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpIn ,
Values : [ ] string { "high" , "low" } ,
} ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "600m" ) ,
v1 . ResourceMemory : resource . MustParse ( "51Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "2" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPodsWithPriorityClasses ( ) ,
} ,
"matching-priorityclass-scoped-quota-OpIn-high" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpIn ,
Values : [ ] string { "high" } ,
} ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "500m" ) ,
v1 . ResourceMemory : resource . MustParse ( "50Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "1" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPodsWithPriorityClasses ( ) ,
} ,
"matching-priorityclass-scoped-quota-OpIn-low" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpIn ,
Values : [ ] string { "low" } ,
} ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "100m" ) ,
v1 . ResourceMemory : resource . MustParse ( "1Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "1" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPodsWithPriorityClasses ( ) ,
} ,
"matching-priorityclass-scoped-quota-OpNotIn-low" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpNotIn ,
Values : [ ] string { "high" } ,
} ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "100m" ) ,
v1 . ResourceMemory : resource . MustParse ( "1Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "1" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPodsWithPriorityClasses ( ) ,
} ,
"non-matching-priorityclass-scoped-quota-OpIn" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpIn ,
Values : [ ] string { "random" } ,
} ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
v1 . ResourceMemory : resource . MustParse ( "0" ) ,
v1 . ResourcePods : resource . MustParse ( "0" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPodsWithPriorityClasses ( ) ,
} ,
"non-matching-priorityclass-scoped-quota-OpNotIn" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpNotIn ,
Values : [ ] string { "random" } ,
} ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "200m" ) ,
v1 . ResourceMemory : resource . MustParse ( "2Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "2" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPods ( ) ,
} ,
"matching-priorityclass-scoped-quota-OpDoesNotExist" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
ScopeSelector : & v1 . ScopeSelector {
MatchExpressions : [ ] v1 . ScopedResourceSelectorRequirement {
{
ScopeName : v1 . ResourceQuotaScopePriorityClass ,
Operator : v1 . ScopeSelectorOpDoesNotExist ,
} ,
} ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "200m" ) ,
v1 . ResourceMemory : resource . MustParse ( "2Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "2" ) ,
} ,
} ,
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPods ( ) ,
} ,
2017-10-27 15:07:53 +00:00
"pods" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta { Name : "quota" , Namespace : "testing" } ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
} ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "5" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "200m" ) ,
v1 . ResourceMemory : resource . MustParse ( "2Gi" ) ,
v1 . ResourcePods : resource . MustParse ( "2" ) ,
} ,
2016-08-05 22:10:09 +00:00
} ,
2017-10-27 15:07:53 +00:00
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : newTestPods ( ) ,
2016-08-05 22:10:09 +00:00
} ,
2017-10-27 15:07:53 +00:00
"quota-spec-hard-updated" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta {
Namespace : "default" ,
Name : "rq" ,
} ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
} ,
} ,
Status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "3" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
} ,
} ,
2016-08-05 22:10:09 +00:00
} ,
2017-10-27 15:07:53 +00:00
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
} ,
2016-08-05 22:10:09 +00:00
} ,
2017-10-27 15:07:53 +00:00
expectedActionSet : sets . NewString (
strings . Join ( [ ] string { "update" , "resourcequotas" , "status" } , "-" ) ,
) ,
items : [ ] runtime . Object { } ,
2016-08-05 22:10:09 +00:00
} ,
2017-10-27 15:07:53 +00:00
"quota-unchanged" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta {
Namespace : "default" ,
Name : "rq" ,
} ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
} ,
} ,
Status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
} ,
} ,
2016-08-05 22:10:09 +00:00
} ,
2017-10-27 15:07:53 +00:00
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
} ,
2016-08-05 22:10:09 +00:00
} ,
2017-10-27 15:07:53 +00:00
expectedActionSet : sets . NewString ( ) ,
items : [ ] runtime . Object { } ,
2016-08-05 22:10:09 +00:00
} ,
2019-03-14 03:13:51 +00:00
"quota-missing-status-with-calculation-error" : {
errorGVR : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta {
Namespace : "default" ,
Name : "rq" ,
} ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourcePods : resource . MustParse ( "1" ) ,
} ,
} ,
Status : v1 . ResourceQuotaStatus { } ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourcePods : resource . MustParse ( "1" ) ,
} ,
} ,
expectedError : "error listing" ,
expectedActionSet : sets . NewString ( "update-resourcequotas-status" ) ,
items : [ ] runtime . Object { } ,
} ,
"quota-missing-status-with-partial-calculation-error" : {
gvr : v1 . SchemeGroupVersion . WithResource ( "configmaps" ) ,
errorGVR : v1 . SchemeGroupVersion . WithResource ( "pods" ) ,
quota : v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta {
Namespace : "default" ,
Name : "rq" ,
} ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourcePods : resource . MustParse ( "1" ) ,
v1 . ResourceConfigMaps : resource . MustParse ( "1" ) ,
} ,
} ,
Status : v1 . ResourceQuotaStatus { } ,
} ,
status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourcePods : resource . MustParse ( "1" ) ,
v1 . ResourceConfigMaps : resource . MustParse ( "1" ) ,
} ,
Used : v1 . ResourceList {
v1 . ResourceConfigMaps : resource . MustParse ( "0" ) ,
} ,
} ,
expectedError : "error listing" ,
expectedActionSet : sets . NewString ( "update-resourcequotas-status" ) ,
items : [ ] runtime . Object { } ,
} ,
2016-08-05 22:10:09 +00:00
}
2017-10-27 15:07:53 +00:00
for testName , testCase := range testCases {
kubeClient := fake . NewSimpleClientset ( & testCase . quota )
listersForResourceConfig := map [ schema . GroupVersionResource ] cache . GenericLister {
2019-03-14 03:13:51 +00:00
testCase . gvr : newGenericLister ( testCase . gvr . GroupResource ( ) , testCase . items ) ,
testCase . errorGVR : newErrorLister ( ) ,
2016-08-05 22:10:09 +00:00
}
2019-03-14 01:56:39 +00:00
qc := setupQuotaController ( t , kubeClient , mockListerForResourceFunc ( listersForResourceConfig ) , mockDiscoveryFunc )
2017-10-27 15:07:53 +00:00
defer close ( qc . stop )
2016-08-05 22:10:09 +00:00
2017-10-27 15:07:53 +00:00
if err := qc . syncResourceQuota ( & testCase . quota ) ; err != nil {
2019-03-14 03:13:51 +00:00
if len ( testCase . expectedError ) == 0 || ! strings . Contains ( err . Error ( ) , testCase . expectedError ) {
t . Fatalf ( "test: %s, unexpected error: %v" , testName , err )
}
} else if len ( testCase . expectedError ) > 0 {
t . Fatalf ( "test: %s, expected error %q, got none" , testName , testCase . expectedError )
2016-08-05 22:10:09 +00:00
}
2017-10-27 15:07:53 +00:00
actionSet := sets . NewString ( )
for _ , action := range kubeClient . Actions ( ) {
actionSet . Insert ( strings . Join ( [ ] string { action . GetVerb ( ) , action . GetResource ( ) . Resource , action . GetSubresource ( ) } , "-" ) )
}
if ! actionSet . HasAll ( testCase . expectedActionSet . List ( ) ... ) {
t . Errorf ( "test: %s,\nExpected actions:\n%v\n but got:\n%v\nDifference:\n%v" , testName , testCase . expectedActionSet , actionSet , testCase . expectedActionSet . Difference ( actionSet ) )
2016-08-05 22:10:09 +00:00
}
2015-04-16 21:46:27 +00:00
2018-11-28 17:38:41 +00:00
var usage * v1 . ResourceQuota
actions := kubeClient . Actions ( )
for i := len ( actions ) - 1 ; i >= 0 ; i -- {
if updateAction , ok := actions [ i ] . ( core . UpdateAction ) ; ok {
usage = updateAction . GetObject ( ) . ( * v1 . ResourceQuota )
break
}
}
if usage == nil {
t . Errorf ( "test: %s,\nExpected update action usage, got none: actions:\n%v" , testName , actions )
}
2015-04-16 21:46:27 +00:00
2017-10-27 15:07:53 +00:00
// ensure usage is as expected
if len ( usage . Status . Hard ) != len ( testCase . status . Hard ) {
t . Errorf ( "test: %s, status hard lengths do not match" , testName )
}
if len ( usage . Status . Used ) != len ( testCase . status . Used ) {
t . Errorf ( "test: %s, status used lengths do not match" , testName )
}
for k , v := range testCase . status . Hard {
actual := usage . Status . Hard [ k ]
actualValue := actual . String ( )
expectedValue := v . String ( )
if expectedValue != actualValue {
t . Errorf ( "test: %s, Usage Hard: Key: %v, Expected: %v, Actual: %v" , testName , k , expectedValue , actualValue )
}
}
for k , v := range testCase . status . Used {
actual := usage . Status . Used [ k ]
actualValue := actual . String ( )
expectedValue := v . String ( )
if expectedValue != actualValue {
t . Errorf ( "test: %s, Usage Used: Key: %v, Expected: %v, Actual: %v" , testName , k , expectedValue , actualValue )
}
}
2015-04-17 20:59:54 +00:00
}
}
2016-07-18 19:48:37 +00:00
func TestAddQuota ( t * testing . T ) {
kubeClient := fake . NewSimpleClientset ( )
2017-10-27 15:07:53 +00:00
gvr := v1 . SchemeGroupVersion . WithResource ( "pods" )
listersForResourceConfig := map [ schema . GroupVersionResource ] cache . GenericLister {
gvr : newGenericLister ( gvr . GroupResource ( ) , newTestPods ( ) ) ,
2016-07-18 19:48:37 +00:00
}
2019-03-14 01:56:39 +00:00
qc := setupQuotaController ( t , kubeClient , mockListerForResourceFunc ( listersForResourceConfig ) , mockDiscoveryFunc )
2017-10-27 15:07:53 +00:00
defer close ( qc . stop )
2016-07-18 19:48:37 +00:00
testCases := [ ] struct {
2017-10-27 15:07:53 +00:00
name string
2016-11-18 20:50:17 +00:00
quota * v1 . ResourceQuota
2016-07-18 19:48:37 +00:00
expectedPriority bool
} {
{
name : "no status" ,
expectedPriority : true ,
2016-11-18 20:50:17 +00:00
quota : & v1 . ResourceQuota {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-07-18 19:48:37 +00:00
Namespace : "default" ,
Name : "rq" ,
} ,
2016-11-18 20:50:17 +00:00
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
} ,
} ,
{
name : "status, no usage" ,
expectedPriority : true ,
2016-11-18 20:50:17 +00:00
quota : & v1 . ResourceQuota {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-07-18 19:48:37 +00:00
Namespace : "default" ,
Name : "rq" ,
} ,
2016-11-18 20:50:17 +00:00
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
2016-11-18 20:50:17 +00:00
Status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
} ,
} ,
2018-01-30 01:29:22 +00:00
{
name : "status, no usage(to validate it works for extended resources)" ,
expectedPriority : true ,
quota : & v1 . ResourceQuota {
ObjectMeta : metav1 . ObjectMeta {
Namespace : "default" ,
Name : "rq" ,
} ,
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
"requests.example/foobars.example.com" : resource . MustParse ( "4" ) ,
} ,
} ,
Status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
"requests.example/foobars.example.com" : resource . MustParse ( "4" ) ,
} ,
} ,
} ,
} ,
2016-07-18 19:48:37 +00:00
{
name : "status, mismatch" ,
expectedPriority : true ,
2016-11-18 20:50:17 +00:00
quota : & v1 . ResourceQuota {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-07-18 19:48:37 +00:00
Namespace : "default" ,
Name : "rq" ,
} ,
2016-11-18 20:50:17 +00:00
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
2016-11-18 20:50:17 +00:00
Status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "6" ) ,
2016-07-18 19:48:37 +00:00
} ,
2016-11-18 20:50:17 +00:00
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
} ,
} ,
{
2017-10-27 15:07:53 +00:00
name : "status, missing usage, but don't care (no informer)" ,
2016-07-18 19:48:37 +00:00
expectedPriority : false ,
2016-11-18 20:50:17 +00:00
quota : & v1 . ResourceQuota {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-07-18 19:48:37 +00:00
Namespace : "default" ,
Name : "rq" ,
} ,
2016-11-18 20:50:17 +00:00
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
2018-01-30 01:29:22 +00:00
"foobars.example.com" : resource . MustParse ( "4" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
2016-11-18 20:50:17 +00:00
Status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
2018-01-30 01:29:22 +00:00
"foobars.example.com" : resource . MustParse ( "4" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
} ,
} ,
{
name : "ready" ,
expectedPriority : false ,
2016-11-18 20:50:17 +00:00
quota : & v1 . ResourceQuota {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-07-18 19:48:37 +00:00
Namespace : "default" ,
Name : "rq" ,
} ,
2016-11-18 20:50:17 +00:00
Spec : v1 . ResourceQuotaSpec {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
2016-11-18 20:50:17 +00:00
Status : v1 . ResourceQuotaStatus {
Hard : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "4" ) ,
2016-07-18 19:48:37 +00:00
} ,
2016-11-18 20:50:17 +00:00
Used : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "0" ) ,
2016-07-18 19:48:37 +00:00
} ,
} ,
} ,
} ,
}
for _ , tc := range testCases {
2017-10-27 15:07:53 +00:00
qc . addQuota ( tc . quota )
2016-07-18 19:48:37 +00:00
if tc . expectedPriority {
2017-10-27 15:07:53 +00:00
if e , a := 1 , qc . missingUsageQueue . Len ( ) ; e != a {
2016-07-18 19:48:37 +00:00
t . Errorf ( "%s: expected %v, got %v" , tc . name , e , a )
}
2017-10-27 15:07:53 +00:00
if e , a := 0 , qc . queue . Len ( ) ; e != a {
2016-07-18 19:48:37 +00:00
t . Errorf ( "%s: expected %v, got %v" , tc . name , e , a )
}
} else {
2017-10-27 15:07:53 +00:00
if e , a := 0 , qc . missingUsageQueue . Len ( ) ; e != a {
2016-07-18 19:48:37 +00:00
t . Errorf ( "%s: expected %v, got %v" , tc . name , e , a )
}
2017-10-27 15:07:53 +00:00
if e , a := 1 , qc . queue . Len ( ) ; e != a {
2016-07-18 19:48:37 +00:00
t . Errorf ( "%s: expected %v, got %v" , tc . name , e , a )
}
}
2017-10-27 15:07:53 +00:00
for qc . missingUsageQueue . Len ( ) > 0 {
key , _ := qc . missingUsageQueue . Get ( )
qc . missingUsageQueue . Done ( key )
2016-07-18 19:48:37 +00:00
}
2017-10-27 15:07:53 +00:00
for qc . queue . Len ( ) > 0 {
key , _ := qc . queue . Get ( )
qc . queue . Done ( key )
2016-07-18 19:48:37 +00:00
}
}
}
2019-03-14 01:56:39 +00:00
// TestDiscoverySync ensures that a discovery client error
// will not cause the quota controller to block infinitely.
func TestDiscoverySync ( t * testing . T ) {
serverResources := [ ] * metav1 . APIResourceList {
{
GroupVersion : "v1" ,
APIResources : [ ] metav1 . APIResource {
{ Name : "pods" , Namespaced : true , Kind : "Pod" , Verbs : metav1 . Verbs { "create" , "delete" , "list" , "watch" } } ,
} ,
} ,
}
unsyncableServerResources := [ ] * metav1 . APIResourceList {
{
GroupVersion : "v1" ,
APIResources : [ ] metav1 . APIResource {
{ Name : "pods" , Namespaced : true , Kind : "Pod" , Verbs : metav1 . Verbs { "create" , "delete" , "list" , "watch" } } ,
{ Name : "secrets" , Namespaced : true , Kind : "Secret" , Verbs : metav1 . Verbs { "create" , "delete" , "list" , "watch" } } ,
} ,
} ,
}
fakeDiscoveryClient := & fakeServerResources {
PreferredResources : serverResources ,
Error : nil ,
Lock : sync . Mutex { } ,
InterfaceUsedCount : 0 ,
}
testHandler := & fakeActionHandler {
response : map [ string ] FakeResponse {
"GET" + "/api/v1/pods" : {
200 ,
[ ] byte ( "{}" ) ,
} ,
"GET" + "/api/v1/secrets" : {
404 ,
[ ] byte ( "{}" ) ,
} ,
} ,
}
srv , clientConfig := testServerAndClientConfig ( testHandler . ServeHTTP )
defer srv . Close ( )
clientConfig . ContentConfig . NegotiatedSerializer = nil
kubeClient , err := kubernetes . NewForConfig ( clientConfig )
if err != nil {
t . Fatal ( err )
}
pods := schema . GroupVersionResource { Group : "" , Version : "v1" , Resource : "pods" }
secrets := schema . GroupVersionResource { Group : "" , Version : "v1" , Resource : "secrets" }
listersForResourceConfig := map [ schema . GroupVersionResource ] cache . GenericLister {
pods : newGenericLister ( pods . GroupResource ( ) , [ ] runtime . Object { } ) ,
secrets : newGenericLister ( secrets . GroupResource ( ) , [ ] runtime . Object { } ) ,
}
qc := setupQuotaController ( t , kubeClient , mockListerForResourceFunc ( listersForResourceConfig ) , fakeDiscoveryClient . ServerPreferredNamespacedResources )
defer close ( qc . stop )
stopSync := make ( chan struct { } )
defer close ( stopSync )
// The pseudo-code of Sync():
// Sync(client, period, stopCh):
// wait.Until() loops with `period` until the `stopCh` is closed :
// GetQuotableResources()
// resyncMonitors()
// controller.WaitForCacheSync() loops with `syncedPollPeriod` (hardcoded to 100ms), until either its stop channel is closed after `period`, or all caches synced.
//
// Setting the period to 200ms allows the WaitForCacheSync() to check
// for cache sync ~2 times in every wait.Until() loop.
//
// The 1s sleep in the test allows GetQuotableResources and
// resyncMonitors to run ~5 times to ensure the changes to the
// fakeDiscoveryClient are picked up.
go qc . Sync ( fakeDiscoveryClient . ServerPreferredNamespacedResources , 200 * time . Millisecond , stopSync )
// Wait until the sync discovers the initial resources
time . Sleep ( 1 * time . Second )
err = expectSyncNotBlocked ( fakeDiscoveryClient , & qc . workerLock )
if err != nil {
t . Fatalf ( "Expected quotacontroller.Sync to be running but it is blocked: %v" , err )
}
// Simulate the discovery client returning an error
fakeDiscoveryClient . setPreferredResources ( nil )
fakeDiscoveryClient . setError ( fmt . Errorf ( "Error calling discoveryClient.ServerPreferredResources()" ) )
// Wait until sync discovers the change
time . Sleep ( 1 * time . Second )
// Remove the error from being returned and see if the quota sync is still working
fakeDiscoveryClient . setPreferredResources ( serverResources )
fakeDiscoveryClient . setError ( nil )
err = expectSyncNotBlocked ( fakeDiscoveryClient , & qc . workerLock )
if err != nil {
t . Fatalf ( "Expected quotacontroller.Sync to still be running but it is blocked: %v" , err )
}
// Simulate the discovery client returning a resource the restmapper can resolve, but will not sync caches
fakeDiscoveryClient . setPreferredResources ( unsyncableServerResources )
fakeDiscoveryClient . setError ( nil )
// Wait until sync discovers the change
time . Sleep ( 1 * time . Second )
// Put the resources back to normal and ensure quota sync recovers
fakeDiscoveryClient . setPreferredResources ( serverResources )
fakeDiscoveryClient . setError ( nil )
err = expectSyncNotBlocked ( fakeDiscoveryClient , & qc . workerLock )
if err != nil {
t . Fatalf ( "Expected quotacontroller.Sync to still be running but it is blocked: %v" , err )
}
}
// testServerAndClientConfig returns a server that listens and a config that can reference it
func testServerAndClientConfig ( handler func ( http . ResponseWriter , * http . Request ) ) ( * httptest . Server , * rest . Config ) {
srv := httptest . NewServer ( http . HandlerFunc ( handler ) )
config := & rest . Config {
Host : srv . URL ,
}
return srv , config
}
func expectSyncNotBlocked ( fakeDiscoveryClient * fakeServerResources , workerLock * sync . RWMutex ) error {
before := fakeDiscoveryClient . getInterfaceUsedCount ( )
t := 1 * time . Second
time . Sleep ( t )
after := fakeDiscoveryClient . getInterfaceUsedCount ( )
if before == after {
return fmt . Errorf ( "discoveryClient.ServerPreferredResources() called %d times over %v" , after - before , t )
}
workerLockAcquired := make ( chan struct { } )
go func ( ) {
workerLock . Lock ( )
workerLock . Unlock ( )
close ( workerLockAcquired )
} ( )
select {
case <- workerLockAcquired :
return nil
case <- time . After ( t ) :
return fmt . Errorf ( "workerLock blocked for at least %v" , t )
}
}
type fakeServerResources struct {
PreferredResources [ ] * metav1 . APIResourceList
Error error
Lock sync . Mutex
InterfaceUsedCount int
}
func ( _ * fakeServerResources ) ServerResourcesForGroupVersion ( groupVersion string ) ( * metav1 . APIResourceList , error ) {
return nil , nil
}
func ( _ * fakeServerResources ) ServerResources ( ) ( [ ] * metav1 . APIResourceList , error ) {
return nil , nil
}
func ( _ * fakeServerResources ) ServerPreferredResources ( ) ( [ ] * metav1 . APIResourceList , error ) {
return nil , nil
}
func ( f * fakeServerResources ) setPreferredResources ( resources [ ] * metav1 . APIResourceList ) {
f . Lock . Lock ( )
defer f . Lock . Unlock ( )
f . PreferredResources = resources
}
func ( f * fakeServerResources ) setError ( err error ) {
f . Lock . Lock ( )
defer f . Lock . Unlock ( )
f . Error = err
}
func ( f * fakeServerResources ) getInterfaceUsedCount ( ) int {
f . Lock . Lock ( )
defer f . Lock . Unlock ( )
return f . InterfaceUsedCount
}
func ( f * fakeServerResources ) ServerPreferredNamespacedResources ( ) ( [ ] * metav1 . APIResourceList , error ) {
f . Lock . Lock ( )
defer f . Lock . Unlock ( )
f . InterfaceUsedCount ++
return f . PreferredResources , f . Error
}
// fakeAction records information about requests to aid in testing.
type fakeAction struct {
method string
path string
query string
}
// String returns method=path to aid in testing
func ( f * fakeAction ) String ( ) string {
return strings . Join ( [ ] string { f . method , f . path } , "=" )
}
type FakeResponse struct {
statusCode int
content [ ] byte
}
// fakeActionHandler holds a list of fakeActions received
type fakeActionHandler struct {
// statusCode and content returned by this handler for different method + path.
response map [ string ] FakeResponse
lock sync . Mutex
actions [ ] fakeAction
}
// ServeHTTP logs the action that occurred and always returns the associated status code
func ( f * fakeActionHandler ) ServeHTTP ( response http . ResponseWriter , request * http . Request ) {
func ( ) {
f . lock . Lock ( )
defer f . lock . Unlock ( )
f . actions = append ( f . actions , fakeAction { method : request . Method , path : request . URL . Path , query : request . URL . RawQuery } )
fakeResponse , ok := f . response [ request . Method + request . URL . Path ]
if ! ok {
fakeResponse . statusCode = 200
fakeResponse . content = [ ] byte ( "{\"kind\": \"List\"}" )
}
response . Header ( ) . Set ( "Content-Type" , "application/json" )
response . WriteHeader ( fakeResponse . statusCode )
response . Write ( fakeResponse . content )
} ( )
// This is to allow the fakeActionHandler to simulate a watch being opened
if strings . Contains ( request . URL . RawQuery , "watch=true" ) {
hijacker , ok := response . ( http . Hijacker )
if ! ok {
return
}
connection , _ , err := hijacker . Hijack ( )
if err != nil {
return
}
defer connection . Close ( )
time . Sleep ( 30 * time . Second )
}
}