|
1 | 1 | package apiutil_test
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "errors" |
5 | 4 | "time"
|
6 | 5 |
|
7 | 6 | . "github.com/onsi/ginkgo"
|
8 | 7 | . "github.com/onsi/gomega"
|
| 8 | + "github.com/onsi/gomega/format" |
| 9 | + "github.com/onsi/gomega/types" |
9 | 10 | "golang.org/x/time/rate"
|
10 | 11 | "k8s.io/apimachinery/pkg/api/meta"
|
11 | 12 | "k8s.io/apimachinery/pkg/runtime/schema"
|
@@ -57,53 +58,49 @@ var _ = Describe("Dynamic REST Mapper", func() {
|
57 | 58 | })
|
58 | 59 |
|
59 | 60 | It("should reload if not present in the cache", func() {
|
60 |
| - By("reading successfully once") |
| 61 | + By("reading target successfully once") |
61 | 62 | Expect(callWithTarget()).To(Succeed())
|
62 |
| - Expect(callWithOther()).NotTo(Succeed()) |
63 | 63 |
|
64 |
| - By("asking for a something that didn't exist previously after adding it to the mapper") |
| 64 | + By("reading other not successfully") |
| 65 | + count := 0 |
65 | 66 | addToMapper = func(baseMapper *meta.DefaultRESTMapper) {
|
| 67 | + count++ |
66 | 68 | baseMapper.Add(targetGVK, meta.RESTScopeNamespace)
|
67 |
| - baseMapper.Add(secondGVK, meta.RESTScopeNamespace) |
68 | 69 | }
|
69 |
| - Expect(callWithOther()).To(Succeed()) |
70 |
| - Expect(callWithTarget()).To(Succeed()) |
71 |
| - }) |
| 70 | + Expect(callWithOther()).To(beNoMatchError()) |
| 71 | + Expect(count).To(Equal(1), "should reload exactly once") |
72 | 72 |
|
73 |
| - It("should rate-limit reloads so that we don't get more than a certain number per second", func() { |
74 |
| - By("setting a small limit") |
75 |
| - *lim = *rate.NewLimiter(rate.Limit(1), 1) |
76 |
| - |
77 |
| - By("forcing a reload after changing the mapper") |
| 73 | + By("reading both successfully now") |
78 | 74 | addToMapper = func(baseMapper *meta.DefaultRESTMapper) {
|
| 75 | + baseMapper.Add(targetGVK, meta.RESTScopeNamespace) |
79 | 76 | baseMapper.Add(secondGVK, meta.RESTScopeNamespace)
|
80 | 77 | }
|
81 | 78 | Expect(callWithOther()).To(Succeed())
|
82 |
| - |
83 |
| - By("calling another time that would need a requery and failing") |
84 |
| - Eventually(func() bool { |
85 |
| - return errors.As(callWithTarget(), &apiutil.ErrRateLimited{}) |
86 |
| - }, "10s").Should(BeTrue()) |
| 79 | + Expect(callWithTarget()).To(Succeed()) |
87 | 80 | })
|
88 | 81 |
|
89 |
| - It("should rate-limit then allow more at 1rps", func() { |
| 82 | + It("should rate-limit then allow more at configured rate", func() { |
90 | 83 | By("setting a small limit")
|
91 |
| - *lim = *rate.NewLimiter(rate.Limit(1), 1) |
| 84 | + *lim = *rate.NewLimiter(rate.Every(100*time.Millisecond), 1) |
92 | 85 |
|
93 | 86 | By("forcing a reload after changing the mapper")
|
94 | 87 | addToMapper = func(baseMapper *meta.DefaultRESTMapper) {
|
95 | 88 | baseMapper.Add(secondGVK, meta.RESTScopeNamespace)
|
96 | 89 | }
|
97 |
| - |
98 |
| - By("calling twice to trigger rate limiting") |
99 | 90 | Expect(callWithOther()).To(Succeed())
|
100 |
| - Expect(callWithTarget()).NotTo(Succeed()) |
101 | 91 |
|
102 |
| - // by 2nd call loop should succeed because we canceled our 1st rate-limited token, then waited a full second |
103 |
| - By("calling until no longer rate-limited, 2nd call should succeed") |
104 |
| - Eventually(func() bool { |
105 |
| - return errors.As(callWithTarget(), &apiutil.ErrRateLimited{}) |
106 |
| - }, "2.5s", "1s").Should(BeFalse()) |
| 92 | + By("calling another time to trigger rate limiting") |
| 93 | + addToMapper = func(baseMapper *meta.DefaultRESTMapper) { |
| 94 | + baseMapper.Add(targetGVK, meta.RESTScopeNamespace) |
| 95 | + } |
| 96 | + // if call consistently fails, we are sure, that it was rate-limited, |
| 97 | + // otherwise it would have reloaded and succeeded |
| 98 | + Consistently(callWithTarget, "90ms", "10ms").Should(beNoMatchError()) |
| 99 | + |
| 100 | + By("calling until no longer rate-limited") |
| 101 | + // once call succeeds, we are sure, that it was no longer rate-limited, |
| 102 | + // as it was allowed to reload and found matching kind/resource |
| 103 | + Eventually(callWithTarget, "30ms", "10ms").Should(And(Succeed(), Not(beNoMatchError()))) |
107 | 104 | })
|
108 | 105 |
|
109 | 106 | It("should avoid reloading twice if two requests for the same thing come in", func() {
|
@@ -251,3 +248,25 @@ var _ = Describe("Dynamic REST Mapper", func() {
|
251 | 248 | })
|
252 | 249 | })
|
253 | 250 | })
|
| 251 | + |
| 252 | +func beNoMatchError() types.GomegaMatcher { |
| 253 | + return noMatchErrorMatcher{} |
| 254 | +} |
| 255 | + |
| 256 | +type noMatchErrorMatcher struct{} |
| 257 | + |
| 258 | +func (k noMatchErrorMatcher) Match(actual interface{}) (success bool, err error) { |
| 259 | + actualErr, actualOk := actual.(error) |
| 260 | + if !actualOk { |
| 261 | + return false, nil |
| 262 | + } |
| 263 | + |
| 264 | + return meta.IsNoMatchError(actualErr), nil |
| 265 | +} |
| 266 | + |
| 267 | +func (k noMatchErrorMatcher) FailureMessage(actual interface{}) (message string) { |
| 268 | + return format.Message(actual, "to be a NoMatchError") |
| 269 | +} |
| 270 | +func (k noMatchErrorMatcher) NegatedFailureMessage(actual interface{}) (message string) { |
| 271 | + return format.Message(actual, "not to be a NoMatchError") |
| 272 | +} |
0 commit comments