admission_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package eventratelimit
  14. import (
  15. "testing"
  16. "time"
  17. "k8s.io/apimachinery/pkg/api/errors"
  18. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  19. "k8s.io/apimachinery/pkg/types"
  20. "k8s.io/apimachinery/pkg/util/clock"
  21. "k8s.io/apiserver/pkg/admission"
  22. "k8s.io/apiserver/pkg/authentication/user"
  23. api "k8s.io/kubernetes/pkg/apis/core"
  24. eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
  25. )
  26. const (
  27. qps = 1
  28. eventKind = "Event"
  29. nonEventKind = "NonEvent"
  30. )
  31. // attributesForRequest generates the admission.Attributes that for the specified request
  32. func attributesForRequest(rq request) admission.Attributes {
  33. return admission.NewAttributesRecord(
  34. rq.event,
  35. nil,
  36. api.Kind(rq.kind).WithVersion("version"),
  37. rq.namespace,
  38. "name",
  39. api.Resource("resource").WithVersion("version"),
  40. "",
  41. admission.Create,
  42. &metav1.CreateOptions{},
  43. rq.dryRun,
  44. &user.DefaultInfo{Name: rq.username})
  45. }
  46. type request struct {
  47. kind string
  48. namespace string
  49. username string
  50. event *api.Event
  51. delay time.Duration
  52. accepted bool
  53. dryRun bool
  54. }
  55. func newRequest(kind string) request {
  56. return request{
  57. kind: kind,
  58. accepted: true,
  59. }
  60. }
  61. func newEventRequest() request {
  62. return newRequest(eventKind)
  63. }
  64. func newNonEventRequest() request {
  65. return newRequest(nonEventKind)
  66. }
  67. func (r request) withNamespace(namespace string) request {
  68. r.namespace = namespace
  69. return r
  70. }
  71. func (r request) withEvent(event *api.Event) request {
  72. r.event = event
  73. return r
  74. }
  75. func (r request) withEventComponent(component string) request {
  76. return r.withEvent(&api.Event{
  77. Source: api.EventSource{
  78. Component: component,
  79. },
  80. })
  81. }
  82. func (r request) withDryRun(dryRun bool) request {
  83. r.dryRun = dryRun
  84. return r
  85. }
  86. func (r request) withUser(name string) request {
  87. r.username = name
  88. return r
  89. }
  90. func (r request) blocked() request {
  91. r.accepted = false
  92. return r
  93. }
  94. // withDelay will adjust the clock to simulate the specified delay, in seconds
  95. func (r request) withDelay(delayInSeconds int) request {
  96. r.delay = time.Duration(delayInSeconds) * time.Second
  97. return r
  98. }
  99. // createSourceAndObjectKeyInclusionRequests creates a series of requests that can be used
  100. // to test that a particular part of the event is included in the source+object key
  101. func createSourceAndObjectKeyInclusionRequests(eventFactory func(label string) *api.Event) []request {
  102. return []request{
  103. newEventRequest().withEvent(eventFactory("A")),
  104. newEventRequest().withEvent(eventFactory("A")).blocked(),
  105. newEventRequest().withEvent(eventFactory("B")),
  106. }
  107. }
  108. func TestEventRateLimiting(t *testing.T) {
  109. cases := []struct {
  110. name string
  111. serverBurst int32
  112. namespaceBurst int32
  113. namespaceCacheSize int32
  114. sourceAndObjectBurst int32
  115. sourceAndObjectCacheSize int32
  116. userBurst int32
  117. userCacheSize int32
  118. requests []request
  119. }{
  120. {
  121. name: "event not blocked when tokens available",
  122. serverBurst: 3,
  123. requests: []request{
  124. newEventRequest(),
  125. },
  126. },
  127. {
  128. name: "non-event not blocked",
  129. serverBurst: 3,
  130. requests: []request{
  131. newNonEventRequest(),
  132. },
  133. },
  134. {
  135. name: "event blocked after tokens exhausted",
  136. serverBurst: 3,
  137. requests: []request{
  138. newEventRequest(),
  139. newEventRequest(),
  140. newEventRequest(),
  141. newEventRequest().blocked(),
  142. },
  143. },
  144. {
  145. name: "event not blocked by dry-run requests",
  146. serverBurst: 3,
  147. requests: []request{
  148. newEventRequest(),
  149. newEventRequest(),
  150. newEventRequest().withDryRun(true),
  151. newEventRequest().withDryRun(true),
  152. newEventRequest().withDryRun(true),
  153. newEventRequest().withDryRun(true),
  154. newEventRequest(),
  155. newEventRequest().blocked(),
  156. newEventRequest().withDryRun(true),
  157. },
  158. },
  159. {
  160. name: "non-event not blocked after tokens exhausted",
  161. serverBurst: 3,
  162. requests: []request{
  163. newEventRequest(),
  164. newEventRequest(),
  165. newEventRequest(),
  166. newNonEventRequest(),
  167. },
  168. },
  169. {
  170. name: "non-events should not count against limit",
  171. serverBurst: 3,
  172. requests: []request{
  173. newEventRequest(),
  174. newEventRequest(),
  175. newNonEventRequest(),
  176. newEventRequest(),
  177. },
  178. },
  179. {
  180. name: "event accepted after token refill",
  181. serverBurst: 3,
  182. requests: []request{
  183. newEventRequest(),
  184. newEventRequest(),
  185. newEventRequest(),
  186. newEventRequest().blocked(),
  187. newEventRequest().withDelay(1),
  188. },
  189. },
  190. {
  191. name: "event blocked by namespace limits",
  192. serverBurst: 100,
  193. namespaceBurst: 3,
  194. namespaceCacheSize: 10,
  195. requests: []request{
  196. newEventRequest().withNamespace("A"),
  197. newEventRequest().withNamespace("A"),
  198. newEventRequest().withNamespace("A"),
  199. newEventRequest().withNamespace("A").blocked(),
  200. },
  201. },
  202. {
  203. name: "event from other namespace not blocked",
  204. serverBurst: 100,
  205. namespaceBurst: 3,
  206. namespaceCacheSize: 10,
  207. requests: []request{
  208. newEventRequest().withNamespace("A"),
  209. newEventRequest().withNamespace("A"),
  210. newEventRequest().withNamespace("A"),
  211. newEventRequest().withNamespace("B"),
  212. },
  213. },
  214. {
  215. name: "events from other namespaces should not count against limit",
  216. serverBurst: 100,
  217. namespaceBurst: 3,
  218. namespaceCacheSize: 10,
  219. requests: []request{
  220. newEventRequest().withNamespace("A"),
  221. newEventRequest().withNamespace("A"),
  222. newEventRequest().withNamespace("B"),
  223. newEventRequest().withNamespace("A"),
  224. },
  225. },
  226. {
  227. name: "event accepted after namespace token refill",
  228. serverBurst: 100,
  229. namespaceBurst: 3,
  230. namespaceCacheSize: 10,
  231. requests: []request{
  232. newEventRequest().withNamespace("A"),
  233. newEventRequest().withNamespace("A"),
  234. newEventRequest().withNamespace("A"),
  235. newEventRequest().withNamespace("A").blocked(),
  236. newEventRequest().withNamespace("A").withDelay(1),
  237. },
  238. },
  239. {
  240. name: "event from other namespaces should not clear namespace limits",
  241. serverBurst: 100,
  242. namespaceBurst: 3,
  243. namespaceCacheSize: 10,
  244. requests: []request{
  245. newEventRequest().withNamespace("A"),
  246. newEventRequest().withNamespace("A"),
  247. newEventRequest().withNamespace("A"),
  248. newEventRequest().withNamespace("B"),
  249. newEventRequest().withNamespace("A").blocked(),
  250. },
  251. },
  252. {
  253. name: "namespace limits from lru namespace should clear when cache size exceeded",
  254. serverBurst: 100,
  255. namespaceBurst: 3,
  256. namespaceCacheSize: 2,
  257. requests: []request{
  258. newEventRequest().withNamespace("A"),
  259. newEventRequest().withNamespace("A"),
  260. newEventRequest().withNamespace("B"),
  261. newEventRequest().withNamespace("B"),
  262. newEventRequest().withNamespace("B"),
  263. newEventRequest().withNamespace("A"),
  264. newEventRequest().withNamespace("B").blocked(),
  265. newEventRequest().withNamespace("A").blocked(),
  266. // This should clear out namespace B from the lru cache
  267. newEventRequest().withNamespace("C"),
  268. newEventRequest().withNamespace("A").blocked(),
  269. newEventRequest().withNamespace("B"),
  270. },
  271. },
  272. {
  273. name: "event blocked by source+object limits",
  274. serverBurst: 100,
  275. sourceAndObjectBurst: 3,
  276. sourceAndObjectCacheSize: 10,
  277. requests: []request{
  278. newEventRequest().withEventComponent("A"),
  279. newEventRequest().withEventComponent("A"),
  280. newEventRequest().withEventComponent("A"),
  281. newEventRequest().withEventComponent("A").blocked(),
  282. },
  283. },
  284. {
  285. name: "event from other source+object not blocked",
  286. serverBurst: 100,
  287. sourceAndObjectBurst: 3,
  288. sourceAndObjectCacheSize: 10,
  289. requests: []request{
  290. newEventRequest().withEventComponent("A"),
  291. newEventRequest().withEventComponent("A"),
  292. newEventRequest().withEventComponent("A"),
  293. newEventRequest().withEventComponent("B"),
  294. },
  295. },
  296. {
  297. name: "events from other source+object should not count against limit",
  298. serverBurst: 100,
  299. sourceAndObjectBurst: 3,
  300. sourceAndObjectCacheSize: 10,
  301. requests: []request{
  302. newEventRequest().withEventComponent("A"),
  303. newEventRequest().withEventComponent("A"),
  304. newEventRequest().withEventComponent("B"),
  305. newEventRequest().withEventComponent("A"),
  306. },
  307. },
  308. {
  309. name: "event accepted after source+object token refill",
  310. serverBurst: 100,
  311. sourceAndObjectBurst: 3,
  312. sourceAndObjectCacheSize: 10,
  313. requests: []request{
  314. newEventRequest().withEventComponent("A"),
  315. newEventRequest().withEventComponent("A"),
  316. newEventRequest().withEventComponent("A"),
  317. newEventRequest().withEventComponent("A").blocked(),
  318. newEventRequest().withEventComponent("A").withDelay(1),
  319. },
  320. },
  321. {
  322. name: "event from other source+object should not clear source+object limits",
  323. serverBurst: 100,
  324. sourceAndObjectBurst: 3,
  325. sourceAndObjectCacheSize: 10,
  326. requests: []request{
  327. newEventRequest().withEventComponent("A"),
  328. newEventRequest().withEventComponent("A"),
  329. newEventRequest().withEventComponent("A"),
  330. newEventRequest().withEventComponent("B"),
  331. newEventRequest().withEventComponent("A").blocked(),
  332. },
  333. },
  334. {
  335. name: "source+object limits from lru source+object should clear when cache size exceeded",
  336. serverBurst: 100,
  337. sourceAndObjectBurst: 3,
  338. sourceAndObjectCacheSize: 2,
  339. requests: []request{
  340. newEventRequest().withEventComponent("A"),
  341. newEventRequest().withEventComponent("A"),
  342. newEventRequest().withEventComponent("B"),
  343. newEventRequest().withEventComponent("B"),
  344. newEventRequest().withEventComponent("B"),
  345. newEventRequest().withEventComponent("A"),
  346. newEventRequest().withEventComponent("B").blocked(),
  347. newEventRequest().withEventComponent("A").blocked(),
  348. // This should clear out component B from the lru cache
  349. newEventRequest().withEventComponent("C"),
  350. newEventRequest().withEventComponent("A").blocked(),
  351. newEventRequest().withEventComponent("B"),
  352. },
  353. },
  354. {
  355. name: "source host should be included in source+object key",
  356. serverBurst: 100,
  357. sourceAndObjectBurst: 1,
  358. sourceAndObjectCacheSize: 10,
  359. requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
  360. return &api.Event{Source: api.EventSource{Host: label}}
  361. }),
  362. },
  363. {
  364. name: "involved object kind should be included in source+object key",
  365. serverBurst: 100,
  366. sourceAndObjectBurst: 1,
  367. sourceAndObjectCacheSize: 10,
  368. requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
  369. return &api.Event{InvolvedObject: api.ObjectReference{Kind: label}}
  370. }),
  371. },
  372. {
  373. name: "involved object namespace should be included in source+object key",
  374. serverBurst: 100,
  375. sourceAndObjectBurst: 1,
  376. sourceAndObjectCacheSize: 10,
  377. requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
  378. return &api.Event{InvolvedObject: api.ObjectReference{Namespace: label}}
  379. }),
  380. },
  381. {
  382. name: "involved object name should be included in source+object key",
  383. serverBurst: 100,
  384. sourceAndObjectBurst: 1,
  385. sourceAndObjectCacheSize: 10,
  386. requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
  387. return &api.Event{InvolvedObject: api.ObjectReference{Name: label}}
  388. }),
  389. },
  390. {
  391. name: "involved object UID should be included in source+object key",
  392. serverBurst: 100,
  393. sourceAndObjectBurst: 1,
  394. sourceAndObjectCacheSize: 10,
  395. requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
  396. return &api.Event{InvolvedObject: api.ObjectReference{UID: types.UID(label)}}
  397. }),
  398. },
  399. {
  400. name: "involved object APIVersion should be included in source+object key",
  401. serverBurst: 100,
  402. sourceAndObjectBurst: 1,
  403. sourceAndObjectCacheSize: 10,
  404. requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
  405. return &api.Event{InvolvedObject: api.ObjectReference{APIVersion: label}}
  406. }),
  407. },
  408. {
  409. name: "event blocked by user limits",
  410. userBurst: 3,
  411. userCacheSize: 10,
  412. requests: []request{
  413. newEventRequest().withUser("A"),
  414. newEventRequest().withUser("A"),
  415. newEventRequest().withUser("A"),
  416. newEventRequest().withUser("A").blocked(),
  417. },
  418. },
  419. {
  420. name: "event from other user not blocked",
  421. requests: []request{
  422. newEventRequest().withUser("A"),
  423. newEventRequest().withUser("A"),
  424. newEventRequest().withUser("A"),
  425. newEventRequest().withUser("B"),
  426. },
  427. },
  428. {
  429. name: "events from other user should not count against limit",
  430. requests: []request{
  431. newEventRequest().withUser("A"),
  432. newEventRequest().withUser("A"),
  433. newEventRequest().withUser("B"),
  434. newEventRequest().withUser("A"),
  435. },
  436. },
  437. }
  438. for _, tc := range cases {
  439. t.Run(tc.name, func(t *testing.T) {
  440. clock := clock.NewFakeClock(time.Now())
  441. config := &eventratelimitapi.Configuration{}
  442. if tc.serverBurst > 0 {
  443. serverLimit := eventratelimitapi.Limit{
  444. Type: eventratelimitapi.ServerLimitType,
  445. QPS: qps,
  446. Burst: tc.serverBurst,
  447. }
  448. config.Limits = append(config.Limits, serverLimit)
  449. }
  450. if tc.namespaceBurst > 0 {
  451. namespaceLimit := eventratelimitapi.Limit{
  452. Type: eventratelimitapi.NamespaceLimitType,
  453. Burst: tc.namespaceBurst,
  454. QPS: qps,
  455. CacheSize: tc.namespaceCacheSize,
  456. }
  457. config.Limits = append(config.Limits, namespaceLimit)
  458. }
  459. if tc.userBurst > 0 {
  460. userLimit := eventratelimitapi.Limit{
  461. Type: eventratelimitapi.UserLimitType,
  462. Burst: tc.userBurst,
  463. QPS: qps,
  464. CacheSize: tc.userCacheSize,
  465. }
  466. config.Limits = append(config.Limits, userLimit)
  467. }
  468. if tc.sourceAndObjectBurst > 0 {
  469. sourceAndObjectLimit := eventratelimitapi.Limit{
  470. Type: eventratelimitapi.SourceAndObjectLimitType,
  471. Burst: tc.sourceAndObjectBurst,
  472. QPS: qps,
  473. CacheSize: tc.sourceAndObjectCacheSize,
  474. }
  475. config.Limits = append(config.Limits, sourceAndObjectLimit)
  476. }
  477. eventratelimit, err := newEventRateLimit(config, clock)
  478. if err != nil {
  479. t.Fatalf("%v: Could not create EventRateLimit: %v", tc.name, err)
  480. }
  481. for rqIndex, rq := range tc.requests {
  482. if rq.delay > 0 {
  483. clock.Step(rq.delay)
  484. }
  485. attributes := attributesForRequest(rq)
  486. err = eventratelimit.Validate(attributes, nil)
  487. if rq.accepted != (err == nil) {
  488. expectedAction := "admitted"
  489. if !rq.accepted {
  490. expectedAction = "blocked"
  491. }
  492. t.Fatalf("%v: Request %v should have been %v: %v", tc.name, rqIndex, expectedAction, err)
  493. }
  494. if err != nil {
  495. statusErr, ok := err.(*errors.StatusError)
  496. if ok && statusErr.ErrStatus.Code != errors.StatusTooManyRequests {
  497. t.Fatalf("%v: Request %v should yield a 429 response: %v", tc.name, rqIndex, err)
  498. }
  499. }
  500. }
  501. })
  502. }
  503. }