mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
resourcequota: use dynamic informer
The resource quota controller should use a dynamic informer so it can create informer for custom resources.
This commit is contained in:
parent
a8cbb22506
commit
f58c2ae62d
@ -122,6 +122,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/client-go/discovery/cached:go_default_library",
|
"//staging/src/k8s.io/client-go/discovery/cached:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/discovery/cached/memory:go_default_library",
|
"//staging/src/k8s.io/client-go/discovery/cached/memory:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/dynamic/dynamicinformer:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||||
|
@ -43,6 +43,8 @@ import (
|
|||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/apiserver/pkg/util/term"
|
"k8s.io/apiserver/pkg/util/term"
|
||||||
cacheddiscovery "k8s.io/client-go/discovery/cached"
|
cacheddiscovery "k8s.io/client-go/discovery/cached"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
@ -234,6 +236,7 @@ func Run(c *config.CompletedConfig, stopCh <-chan struct{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
controllerContext.InformerFactory.Start(controllerContext.Stop)
|
controllerContext.InformerFactory.Start(controllerContext.Stop)
|
||||||
|
controllerContext.GenericInformerFactory.Start(controllerContext.Stop)
|
||||||
close(controllerContext.InformersStarted)
|
close(controllerContext.InformersStarted)
|
||||||
|
|
||||||
select {}
|
select {}
|
||||||
@ -288,6 +291,10 @@ type ControllerContext struct {
|
|||||||
// InformerFactory gives access to informers for the controller.
|
// InformerFactory gives access to informers for the controller.
|
||||||
InformerFactory informers.SharedInformerFactory
|
InformerFactory informers.SharedInformerFactory
|
||||||
|
|
||||||
|
// GenericInformerFactory gives access to informers for typed resources
|
||||||
|
// and dynamic resources.
|
||||||
|
GenericInformerFactory controller.InformerFactory
|
||||||
|
|
||||||
// ComponentConfig provides access to init options for a given controller
|
// ComponentConfig provides access to init options for a given controller
|
||||||
ComponentConfig kubectrlmgrconfig.KubeControllerManagerConfiguration
|
ComponentConfig kubectrlmgrconfig.KubeControllerManagerConfiguration
|
||||||
|
|
||||||
@ -433,6 +440,9 @@ func CreateControllerContext(s *config.CompletedConfig, rootClientBuilder, clien
|
|||||||
versionedClient := rootClientBuilder.ClientOrDie("shared-informers")
|
versionedClient := rootClientBuilder.ClientOrDie("shared-informers")
|
||||||
sharedInformers := informers.NewSharedInformerFactory(versionedClient, ResyncPeriod(s)())
|
sharedInformers := informers.NewSharedInformerFactory(versionedClient, ResyncPeriod(s)())
|
||||||
|
|
||||||
|
dynamicClient := dynamic.NewForConfigOrDie(rootClientBuilder.ConfigOrDie("dynamic-informers"))
|
||||||
|
dynamicInformers := dynamicinformer.NewDynamicSharedInformerFactory(dynamicClient, ResyncPeriod(s)())
|
||||||
|
|
||||||
// If apiserver is not running we should wait for some time and fail only then. This is particularly
|
// If apiserver is not running we should wait for some time and fail only then. This is particularly
|
||||||
// important when we start apiserver and controller manager at the same time.
|
// important when we start apiserver and controller manager at the same time.
|
||||||
if err := genericcontrollermanager.WaitForAPIServer(versionedClient, 10*time.Second); err != nil {
|
if err := genericcontrollermanager.WaitForAPIServer(versionedClient, 10*time.Second); err != nil {
|
||||||
@ -461,6 +471,7 @@ func CreateControllerContext(s *config.CompletedConfig, rootClientBuilder, clien
|
|||||||
ctx := ControllerContext{
|
ctx := ControllerContext{
|
||||||
ClientBuilder: clientBuilder,
|
ClientBuilder: clientBuilder,
|
||||||
InformerFactory: sharedInformers,
|
InformerFactory: sharedInformers,
|
||||||
|
GenericInformerFactory: controller.NewInformerFactory(sharedInformers, dynamicInformers),
|
||||||
ComponentConfig: s.ComponentConfig,
|
ComponentConfig: s.ComponentConfig,
|
||||||
RESTMapper: restMapper,
|
RESTMapper: restMapper,
|
||||||
AvailableResources: availableResources,
|
AvailableResources: availableResources,
|
||||||
|
@ -23,13 +23,12 @@ package app
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
@ -295,7 +294,7 @@ func startResourceQuotaController(ctx ControllerContext) (http.Handler, bool, er
|
|||||||
QuotaClient: resourceQuotaControllerClient.CoreV1(),
|
QuotaClient: resourceQuotaControllerClient.CoreV1(),
|
||||||
ResourceQuotaInformer: ctx.InformerFactory.Core().V1().ResourceQuotas(),
|
ResourceQuotaInformer: ctx.InformerFactory.Core().V1().ResourceQuotas(),
|
||||||
ResyncPeriod: controller.StaticResyncPeriodFunc(ctx.ComponentConfig.ResourceQuotaController.ResourceQuotaSyncPeriod.Duration),
|
ResyncPeriod: controller.StaticResyncPeriodFunc(ctx.ComponentConfig.ResourceQuotaController.ResourceQuotaSyncPeriod.Duration),
|
||||||
InformerFactory: ctx.InformerFactory,
|
InformerFactory: ctx.GenericInformerFactory,
|
||||||
ReplenishmentResyncPeriod: ctx.ResyncPeriod,
|
ReplenishmentResyncPeriod: ctx.ResyncPeriod,
|
||||||
DiscoveryFunc: discoveryFunc,
|
DiscoveryFunc: discoveryFunc,
|
||||||
IgnoredResourcesFunc: quotaConfiguration.IgnoredResources,
|
IgnoredResourcesFunc: quotaConfiguration.IgnoredResources,
|
||||||
|
@ -121,9 +121,11 @@ func TestController_DiscoveryError(t *testing.T) {
|
|||||||
testDiscovery := FakeDiscoveryWithError{Err: test.discoveryError, PossibleResources: test.possibleResources}
|
testDiscovery := FakeDiscoveryWithError{Err: test.discoveryError, PossibleResources: test.possibleResources}
|
||||||
testClientset := NewFakeClientset(testDiscovery)
|
testClientset := NewFakeClientset(testDiscovery)
|
||||||
testClientBuilder := TestClientBuilder{clientset: testClientset}
|
testClientBuilder := TestClientBuilder{clientset: testClientset}
|
||||||
|
testInformerFactory := informers.NewSharedInformerFactoryWithOptions(testClientset, time.Duration(1))
|
||||||
ctx := ControllerContext{
|
ctx := ControllerContext{
|
||||||
ClientBuilder: testClientBuilder,
|
ClientBuilder: testClientBuilder,
|
||||||
InformerFactory: informers.NewSharedInformerFactoryWithOptions(testClientset, time.Duration(1)),
|
InformerFactory: testInformerFactory,
|
||||||
|
GenericInformerFactory: testInformerFactory,
|
||||||
InformersStarted: make(chan struct{}),
|
InformersStarted: make(chan struct{}),
|
||||||
}
|
}
|
||||||
for funcName, controllerInit := range controllerInitFuncMap {
|
for funcName, controllerInit := range controllerInitFuncMap {
|
||||||
|
@ -47,6 +47,7 @@ go_library(
|
|||||||
"controller_ref_manager.go",
|
"controller_ref_manager.go",
|
||||||
"controller_utils.go",
|
"controller_utils.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
|
"informer_factory.go",
|
||||||
"lookup_cache.go",
|
"lookup_cache.go",
|
||||||
],
|
],
|
||||||
importpath = "k8s.io/kubernetes/pkg/controller",
|
importpath = "k8s.io/kubernetes/pkg/controller",
|
||||||
@ -79,6 +80,8 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/dynamic/dynamicinformer:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/typed/authentication/v1:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes/typed/authentication/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
||||||
|
56
pkg/controller/informer_factory.go
Normal file
56
pkg/controller/informer_factory.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InformerFactory creates informers for each group version resource.
|
||||||
|
type InformerFactory interface {
|
||||||
|
ForResource(resource schema.GroupVersionResource) (informers.GenericInformer, error)
|
||||||
|
Start(stopCh <-chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type informerFactory struct {
|
||||||
|
typedInformerFactory informers.SharedInformerFactory
|
||||||
|
dynamicInformerFactory dynamicinformer.DynamicSharedInformerFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *informerFactory) ForResource(resource schema.GroupVersionResource) (informers.GenericInformer, error) {
|
||||||
|
informer, err := i.typedInformerFactory.ForResource(resource)
|
||||||
|
if err != nil {
|
||||||
|
return i.dynamicInformerFactory.ForResource(resource), nil
|
||||||
|
}
|
||||||
|
return informer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *informerFactory) Start(stopCh <-chan struct{}) {
|
||||||
|
i.typedInformerFactory.Start(stopCh)
|
||||||
|
i.dynamicInformerFactory.Start(stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInformerFactory creates a new InformerFactory which works with both typed
|
||||||
|
// resources and dynamic resources
|
||||||
|
func NewInformerFactory(typedInformerFactory informers.SharedInformerFactory, dynamicInformerFactory dynamicinformer.DynamicSharedInformerFactory) InformerFactory {
|
||||||
|
return &informerFactory{
|
||||||
|
typedInformerFactory: typedInformerFactory,
|
||||||
|
dynamicInformerFactory: dynamicInformerFactory,
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,6 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/discovery:go_default_library",
|
"//staging/src/k8s.io/client-go/discovery:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/informers/core/v1:go_default_library",
|
"//staging/src/k8s.io/client-go/informers/core/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
|
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
|
||||||
|
@ -35,7 +35,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
"k8s.io/client-go/informers"
|
|
||||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
corelisters "k8s.io/client-go/listers/core/v1"
|
corelisters "k8s.io/client-go/listers/core/v1"
|
||||||
@ -52,12 +51,6 @@ type NamespacedResourcesFunc func() ([]*metav1.APIResourceList, error)
|
|||||||
// that may require quota to be recalculated.
|
// that may require quota to be recalculated.
|
||||||
type ReplenishmentFunc func(groupResource schema.GroupResource, namespace string)
|
type ReplenishmentFunc func(groupResource schema.GroupResource, namespace string)
|
||||||
|
|
||||||
// InformerFactory is all the quota system needs to interface with informers.
|
|
||||||
type InformerFactory interface {
|
|
||||||
ForResource(resource schema.GroupVersionResource) (informers.GenericInformer, error)
|
|
||||||
Start(stopCh <-chan struct{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceQuotaControllerOptions holds options for creating a quota controller
|
// ResourceQuotaControllerOptions holds options for creating a quota controller
|
||||||
type ResourceQuotaControllerOptions struct {
|
type ResourceQuotaControllerOptions struct {
|
||||||
// Must have authority to list all quotas, and update quota status
|
// Must have authority to list all quotas, and update quota status
|
||||||
@ -75,7 +68,7 @@ type ResourceQuotaControllerOptions struct {
|
|||||||
// InformersStarted knows if informers were started.
|
// InformersStarted knows if informers were started.
|
||||||
InformersStarted <-chan struct{}
|
InformersStarted <-chan struct{}
|
||||||
// InformerFactory interfaces with informers.
|
// InformerFactory interfaces with informers.
|
||||||
InformerFactory InformerFactory
|
InformerFactory controller.InformerFactory
|
||||||
// Controls full resync of objects monitored for replenishment.
|
// Controls full resync of objects monitored for replenishment.
|
||||||
ReplenishmentResyncPeriod controller.ResyncPeriodFunc
|
ReplenishmentResyncPeriod controller.ResyncPeriodFunc
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ type QuotaMonitor struct {
|
|||||||
resourceChanges workqueue.RateLimitingInterface
|
resourceChanges workqueue.RateLimitingInterface
|
||||||
|
|
||||||
// interfaces with informers
|
// interfaces with informers
|
||||||
informerFactory InformerFactory
|
informerFactory controller.InformerFactory
|
||||||
|
|
||||||
// list of resources to ignore
|
// list of resources to ignore
|
||||||
ignoredResources map[schema.GroupResource]struct{}
|
ignoredResources map[schema.GroupResource]struct{}
|
||||||
@ -101,7 +101,7 @@ type QuotaMonitor struct {
|
|||||||
registry quota.Registry
|
registry quota.Registry
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQuotaMonitor(informersStarted <-chan struct{}, informerFactory InformerFactory, ignoredResources map[schema.GroupResource]struct{}, resyncPeriod controller.ResyncPeriodFunc, replenishmentFunc ReplenishmentFunc, registry quota.Registry) *QuotaMonitor {
|
func NewQuotaMonitor(informersStarted <-chan struct{}, informerFactory controller.InformerFactory, ignoredResources map[schema.GroupResource]struct{}, resyncPeriod controller.ResyncPeriodFunc, replenishmentFunc ReplenishmentFunc, registry quota.Registry) *QuotaMonitor {
|
||||||
return &QuotaMonitor{
|
return &QuotaMonitor{
|
||||||
informersStarted: informersStarted,
|
informersStarted: informersStarted,
|
||||||
informerFactory: informerFactory,
|
informerFactory: informerFactory,
|
||||||
|
@ -27,11 +27,13 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/kubernetes/pkg/quota/v1/evaluator/core"
|
"k8s.io/kubernetes/pkg/quota/v1/evaluator/core"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
"k8s.io/kubernetes/test/utils/crd"
|
||||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
@ -487,6 +489,89 @@ var _ = SIGDescribe("ResourceQuota", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should create a ResourceQuota and capture the life of a custom resource.", func() {
|
||||||
|
By("Creating a Custom Resource Definition")
|
||||||
|
testcrd, err := crd.CreateTestCRD(f)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer testcrd.CleanUp()
|
||||||
|
countResourceName := "count/" + testcrd.Crd.Spec.Names.Plural + "." + testcrd.Crd.Spec.Group
|
||||||
|
// resourcequota controller needs to take 30 seconds at most to detect the new custom resource.
|
||||||
|
// in order to make sure the resourcequota controller knows this resource, we create one test
|
||||||
|
// resourcequota object, and triggering updates on it until the status is updated.
|
||||||
|
quotaName := "quota-for-" + testcrd.Crd.Spec.Names.Plural
|
||||||
|
resourceQuota, err := createResourceQuota(f.ClientSet, f.Namespace.Name, &v1.ResourceQuota{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: quotaName},
|
||||||
|
Spec: v1.ResourceQuotaSpec{
|
||||||
|
Hard: v1.ResourceList{
|
||||||
|
v1.ResourceName(countResourceName): resource.MustParse("0"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
err = updateResourceQuotaUntilUsageAppears(f.ClientSet, f.Namespace.Name, quotaName, v1.ResourceName(countResourceName))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = f.ClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Delete(quotaName, nil)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Counting existing ResourceQuota")
|
||||||
|
c, err := countResourceQuota(f.ClientSet, f.Namespace.Name)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Creating a ResourceQuota")
|
||||||
|
quotaName = "test-quota"
|
||||||
|
resourceQuota = newTestResourceQuota(quotaName)
|
||||||
|
resourceQuota.Spec.Hard[v1.ResourceName(countResourceName)] = resource.MustParse("1")
|
||||||
|
resourceQuota, err = createResourceQuota(f.ClientSet, f.Namespace.Name, resourceQuota)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Ensuring resource quota status is calculated")
|
||||||
|
usedResources := v1.ResourceList{}
|
||||||
|
usedResources[v1.ResourceQuotas] = resource.MustParse(strconv.Itoa(c + 1))
|
||||||
|
usedResources[v1.ResourceName(countResourceName)] = resource.MustParse("0")
|
||||||
|
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Creating a custom resource")
|
||||||
|
resourceClient := testcrd.GetV1DynamicClient()
|
||||||
|
testcr, err := instantiateCustomResource(&unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": testcrd.APIGroup + "/" + testcrd.GetAPIVersions()[0],
|
||||||
|
"kind": testcrd.Crd.Spec.Names.Kind,
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "test-cr-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, resourceClient, testcrd.Crd)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Ensuring resource quota status captures custom resource creation")
|
||||||
|
usedResources = v1.ResourceList{}
|
||||||
|
usedResources[v1.ResourceName(countResourceName)] = resource.MustParse("1")
|
||||||
|
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Creating a second custom resource")
|
||||||
|
_, err = instantiateCustomResource(&unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": testcrd.APIGroup + "/" + testcrd.GetAPIVersions()[0],
|
||||||
|
"kind": testcrd.Crd.Spec.Names.Kind,
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "test-cr-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, resourceClient, testcrd.Crd)
|
||||||
|
// since we only give one quota, this creation should fail.
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
|
||||||
|
By("Deleting a custom resource")
|
||||||
|
err = deleteCustomResource(resourceClient, testcr.GetName())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Ensuring resource quota status released usage")
|
||||||
|
usedResources[v1.ResourceName(countResourceName)] = resource.MustParse("0")
|
||||||
|
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
It("should verify ResourceQuota with terminating scopes.", func() {
|
It("should verify ResourceQuota with terminating scopes.", func() {
|
||||||
By("Creating a ResourceQuota with terminating scope")
|
By("Creating a ResourceQuota with terminating scope")
|
||||||
quotaTerminatingName := "quota-terminating"
|
quotaTerminatingName := "quota-terminating"
|
||||||
@ -1524,3 +1609,29 @@ func waitForResourceQuota(c clientset.Interface, ns, quotaName string, used v1.R
|
|||||||
return true, nil
|
return true, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateResourceQuotaUntilUsageAppears updates the resource quota object until the usage is populated
|
||||||
|
// for the specific resource name.
|
||||||
|
func updateResourceQuotaUntilUsageAppears(c clientset.Interface, ns, quotaName string, resourceName v1.ResourceName) error {
|
||||||
|
return wait.Poll(framework.Poll, 1*time.Minute, func() (bool, error) {
|
||||||
|
resourceQuota, err := c.CoreV1().ResourceQuotas(ns).Get(quotaName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// verify that the quota shows the expected used resource values
|
||||||
|
_, ok := resourceQuota.Status.Used[resourceName]
|
||||||
|
if ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
current := resourceQuota.Spec.Hard[resourceName]
|
||||||
|
current.Add(resource.MustParse("1"))
|
||||||
|
resourceQuota.Spec.Hard[resourceName] = current
|
||||||
|
_, err = c.CoreV1().ResourceQuotas(ns).Update(resourceQuota)
|
||||||
|
// ignoring conflicts since someone else may already updated it.
|
||||||
|
if errors.IsConflict(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user