admission_test.go 16 KB

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