|
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