filter_test.go 25 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. package filter_test
  2. import (
  3. "testing"
  4. "github.com/opencost/opencost/pkg/filter"
  5. "github.com/opencost/opencost/pkg/kubecost"
  6. )
  7. func Test_String_Matches(t *testing.T) {
  8. cases := []struct {
  9. name string
  10. a *kubecost.Allocation
  11. filter filter.Filter[*kubecost.Allocation]
  12. expected bool
  13. }{
  14. {
  15. name: "ClusterID Equals -> true",
  16. a: &kubecost.Allocation{
  17. Properties: &kubecost.AllocationProperties{
  18. Cluster: "cluster-one",
  19. },
  20. },
  21. filter: filter.StringProperty[*kubecost.Allocation]{
  22. Field: kubecost.AllocationClusterProp,
  23. Op: filter.StringEquals,
  24. Value: "cluster-one",
  25. },
  26. expected: true,
  27. },
  28. {
  29. name: "ClusterID StartsWith -> true",
  30. a: &kubecost.Allocation{
  31. Properties: &kubecost.AllocationProperties{
  32. Cluster: "cluster-one",
  33. },
  34. },
  35. filter: filter.StringProperty[*kubecost.Allocation]{
  36. Field: kubecost.AllocationClusterProp,
  37. Op: filter.StringStartsWith,
  38. Value: "cluster",
  39. },
  40. expected: true,
  41. },
  42. {
  43. name: "ClusterID StartsWith -> false",
  44. a: &kubecost.Allocation{
  45. Properties: &kubecost.AllocationProperties{
  46. Cluster: "k8s-one",
  47. },
  48. },
  49. filter: filter.StringProperty[*kubecost.Allocation]{
  50. Field: kubecost.AllocationClusterProp,
  51. Op: filter.StringStartsWith,
  52. Value: "cluster",
  53. },
  54. expected: false,
  55. },
  56. {
  57. name: "ClusterID empty StartsWith '' -> true",
  58. a: &kubecost.Allocation{
  59. Properties: &kubecost.AllocationProperties{
  60. Cluster: "",
  61. },
  62. },
  63. filter: filter.StringProperty[*kubecost.Allocation]{
  64. Field: kubecost.AllocationClusterProp,
  65. Op: filter.StringStartsWith,
  66. Value: "",
  67. },
  68. expected: true,
  69. },
  70. {
  71. name: "ClusterID nonempty StartsWith '' -> true",
  72. a: &kubecost.Allocation{
  73. Properties: &kubecost.AllocationProperties{
  74. Cluster: "abc",
  75. },
  76. },
  77. filter: filter.StringProperty[*kubecost.Allocation]{
  78. Field: kubecost.AllocationClusterProp,
  79. Op: filter.StringStartsWith,
  80. Value: "",
  81. },
  82. expected: true,
  83. },
  84. {
  85. name: "Node Equals -> true",
  86. a: &kubecost.Allocation{
  87. Properties: &kubecost.AllocationProperties{
  88. Node: "node123",
  89. },
  90. },
  91. filter: filter.StringProperty[*kubecost.Allocation]{
  92. Field: kubecost.AllocationNodeProp,
  93. Op: filter.StringEquals,
  94. Value: "node123",
  95. },
  96. expected: true,
  97. },
  98. {
  99. name: "Namespace Equals Unallocated -> true",
  100. a: &kubecost.Allocation{
  101. Properties: &kubecost.AllocationProperties{
  102. Namespace: "",
  103. },
  104. },
  105. filter: filter.StringProperty[*kubecost.Allocation]{
  106. Field: kubecost.AllocationNamespaceProp,
  107. Op: filter.StringEquals,
  108. Value: kubecost.UnallocatedSuffix,
  109. },
  110. expected: true,
  111. },
  112. {
  113. name: "ControllerKind Equals -> true",
  114. a: &kubecost.Allocation{
  115. Properties: &kubecost.AllocationProperties{
  116. ControllerKind: "deployment", // We generally store controller kinds as all lowercase
  117. },
  118. },
  119. filter: filter.StringProperty[*kubecost.Allocation]{
  120. Field: kubecost.AllocationControllerKindProp,
  121. Op: filter.StringEquals,
  122. Value: "deployment",
  123. },
  124. expected: true,
  125. },
  126. {
  127. name: "ControllerName Equals -> true",
  128. a: &kubecost.Allocation{
  129. Properties: &kubecost.AllocationProperties{
  130. Controller: "kc-cost-analyzer",
  131. },
  132. },
  133. filter: filter.StringProperty[*kubecost.Allocation]{
  134. Field: kubecost.AllocationControllerProp,
  135. Op: filter.StringEquals,
  136. Value: "kc-cost-analyzer",
  137. },
  138. expected: true,
  139. },
  140. {
  141. name: "Pod (with UID) Equals -> true",
  142. a: &kubecost.Allocation{
  143. Properties: &kubecost.AllocationProperties{
  144. Pod: "pod-123 UID-ABC",
  145. },
  146. },
  147. filter: filter.StringProperty[*kubecost.Allocation]{
  148. Field: kubecost.AllocationPodProp,
  149. Op: filter.StringEquals,
  150. Value: "pod-123 UID-ABC",
  151. },
  152. expected: true,
  153. },
  154. {
  155. name: "Container Equals -> true",
  156. a: &kubecost.Allocation{
  157. Properties: &kubecost.AllocationProperties{
  158. Container: "cost-model",
  159. },
  160. },
  161. filter: filter.StringProperty[*kubecost.Allocation]{
  162. Field: kubecost.AllocationContainerProp,
  163. Op: filter.StringEquals,
  164. Value: "cost-model",
  165. },
  166. expected: true,
  167. },
  168. {
  169. name: `namespace unallocated -> true`,
  170. a: &kubecost.Allocation{
  171. Properties: &kubecost.AllocationProperties{
  172. Namespace: "",
  173. },
  174. },
  175. filter: filter.StringProperty[*kubecost.Allocation]{
  176. Field: kubecost.AllocationNamespaceProp,
  177. Op: filter.StringEquals,
  178. Value: kubecost.UnallocatedSuffix,
  179. },
  180. expected: true,
  181. },
  182. }
  183. for _, c := range cases {
  184. result := c.filter.Matches(c.a)
  185. if result != c.expected {
  186. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  187. }
  188. }
  189. }
  190. func Test_StringSlice_Matches(t *testing.T) {
  191. cases := []struct {
  192. name string
  193. a *kubecost.Allocation
  194. filter filter.Filter[*kubecost.Allocation]
  195. expected bool
  196. }{
  197. {
  198. name: `services contains -> true`,
  199. a: &kubecost.Allocation{
  200. Properties: &kubecost.AllocationProperties{
  201. Services: []string{"serv1", "serv2"},
  202. },
  203. },
  204. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  205. Field: kubecost.AllocationServiceProp,
  206. Op: filter.StringSliceContains,
  207. Value: "serv2",
  208. },
  209. expected: true,
  210. },
  211. {
  212. name: `services contains -> false`,
  213. a: &kubecost.Allocation{
  214. Properties: &kubecost.AllocationProperties{
  215. Services: []string{"serv1", "serv2"},
  216. },
  217. },
  218. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  219. Field: kubecost.AllocationServiceProp,
  220. Op: filter.StringSliceContains,
  221. Value: "serv3",
  222. },
  223. expected: false,
  224. },
  225. {
  226. name: `services contains unallocated -> false`,
  227. a: &kubecost.Allocation{
  228. Properties: &kubecost.AllocationProperties{
  229. Services: []string{"serv1", "serv2"},
  230. },
  231. },
  232. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  233. Field: kubecost.AllocationServiceProp,
  234. Op: filter.StringSliceContains,
  235. Value: kubecost.UnallocatedSuffix,
  236. },
  237. expected: false,
  238. },
  239. {
  240. name: `services contains unallocated -> true`,
  241. a: &kubecost.Allocation{
  242. Properties: &kubecost.AllocationProperties{
  243. Services: []string{},
  244. },
  245. },
  246. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  247. Field: kubecost.AllocationServiceProp,
  248. Op: filter.StringSliceContains,
  249. Value: kubecost.UnallocatedSuffix,
  250. },
  251. expected: true,
  252. },
  253. }
  254. for _, c := range cases {
  255. result := c.filter.Matches(c.a)
  256. if result != c.expected {
  257. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  258. }
  259. }
  260. }
  261. func Test_StringMap_Matches(t *testing.T) {
  262. cases := []struct {
  263. name string
  264. a *kubecost.Allocation
  265. filter filter.Filter[*kubecost.Allocation]
  266. expected bool
  267. }{
  268. {
  269. name: `label[app]="foo" -> true`,
  270. a: &kubecost.Allocation{
  271. Properties: &kubecost.AllocationProperties{
  272. Labels: map[string]string{
  273. "app": "foo",
  274. },
  275. },
  276. },
  277. filter: filter.StringMapProperty[*kubecost.Allocation]{
  278. Field: kubecost.AllocationLabelProp,
  279. Op: filter.StringMapEquals,
  280. Key: "app",
  281. Value: "foo",
  282. },
  283. expected: true,
  284. },
  285. {
  286. name: `label[app]="foo" -> different value -> false`,
  287. a: &kubecost.Allocation{
  288. Properties: &kubecost.AllocationProperties{
  289. Labels: map[string]string{
  290. "app": "bar",
  291. },
  292. },
  293. },
  294. filter: filter.StringMapProperty[*kubecost.Allocation]{
  295. Field: kubecost.AllocationLabelProp,
  296. Op: filter.StringMapEquals,
  297. Key: "app",
  298. Value: "foo",
  299. },
  300. expected: false,
  301. },
  302. {
  303. name: `label[app]="foo" -> label missing -> false`,
  304. a: &kubecost.Allocation{
  305. Properties: &kubecost.AllocationProperties{
  306. Labels: map[string]string{
  307. "someotherlabel": "someothervalue",
  308. },
  309. },
  310. },
  311. filter: filter.StringMapProperty[*kubecost.Allocation]{
  312. Field: kubecost.AllocationLabelProp,
  313. Op: filter.StringMapEquals,
  314. Key: "app",
  315. Value: "foo",
  316. },
  317. expected: false,
  318. },
  319. {
  320. name: `label[app]=Unallocated -> label missing -> true`,
  321. a: &kubecost.Allocation{
  322. Properties: &kubecost.AllocationProperties{
  323. Labels: map[string]string{
  324. "someotherlabel": "someothervalue",
  325. },
  326. },
  327. },
  328. filter: filter.StringMapProperty[*kubecost.Allocation]{
  329. Field: kubecost.AllocationLabelProp,
  330. Op: filter.StringMapEquals,
  331. Key: "app",
  332. Value: kubecost.UnallocatedSuffix,
  333. },
  334. expected: true,
  335. },
  336. {
  337. name: `label[app]=Unallocated -> label present -> false`,
  338. a: &kubecost.Allocation{
  339. Properties: &kubecost.AllocationProperties{
  340. Labels: map[string]string{
  341. "app": "test",
  342. },
  343. },
  344. },
  345. filter: filter.StringMapProperty[*kubecost.Allocation]{
  346. Field: kubecost.AllocationLabelProp,
  347. Op: filter.StringMapEquals,
  348. Key: "app",
  349. Value: kubecost.UnallocatedSuffix,
  350. },
  351. expected: false,
  352. },
  353. {
  354. name: `annotation[prom_modified_name]="testing123" -> true`,
  355. a: &kubecost.Allocation{
  356. Properties: &kubecost.AllocationProperties{
  357. Annotations: map[string]string{
  358. "prom_modified_name": "testing123",
  359. },
  360. },
  361. },
  362. filter: filter.StringMapProperty[*kubecost.Allocation]{
  363. Field: kubecost.AllocationAnnotationProp,
  364. Op: filter.StringMapEquals,
  365. Key: "prom_modified_name",
  366. Value: "testing123",
  367. },
  368. expected: true,
  369. },
  370. {
  371. name: `annotation[app]="foo" -> different value -> false`,
  372. a: &kubecost.Allocation{
  373. Properties: &kubecost.AllocationProperties{
  374. Annotations: map[string]string{
  375. "app": "bar",
  376. },
  377. },
  378. },
  379. filter: filter.StringMapProperty[*kubecost.Allocation]{
  380. Field: kubecost.AllocationAnnotationProp,
  381. Op: filter.StringMapEquals,
  382. Key: "app",
  383. Value: "foo",
  384. },
  385. expected: false,
  386. },
  387. {
  388. name: `annotation[app]="foo" -> annotation missing -> false`,
  389. a: &kubecost.Allocation{
  390. Properties: &kubecost.AllocationProperties{
  391. Annotations: map[string]string{
  392. "someotherannotation": "someothervalue",
  393. },
  394. },
  395. },
  396. filter: filter.StringMapProperty[*kubecost.Allocation]{
  397. Field: kubecost.AllocationAnnotationProp,
  398. Op: filter.StringMapEquals,
  399. Key: "app",
  400. Value: "foo",
  401. },
  402. expected: false,
  403. },
  404. }
  405. for _, c := range cases {
  406. result := c.filter.Matches(c.a)
  407. if result != c.expected {
  408. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  409. }
  410. }
  411. }
  412. func Test_Not_Matches(t *testing.T) {
  413. cases := []struct {
  414. name string
  415. a *kubecost.Allocation
  416. filter filter.Filter[*kubecost.Allocation]
  417. expected bool
  418. }{
  419. {
  420. name: "Namespace NotEquals -> false",
  421. a: &kubecost.Allocation{
  422. Properties: &kubecost.AllocationProperties{
  423. Namespace: "kube-system",
  424. },
  425. },
  426. filter: filter.Not[*kubecost.Allocation]{
  427. Filter: filter.StringProperty[*kubecost.Allocation]{
  428. Field: kubecost.AllocationNamespaceProp,
  429. Op: filter.StringEquals,
  430. Value: "kube-system",
  431. },
  432. },
  433. expected: false,
  434. },
  435. {
  436. name: "Namespace NotEquals Unallocated -> true",
  437. a: &kubecost.Allocation{
  438. Properties: &kubecost.AllocationProperties{
  439. Namespace: "kube-system",
  440. },
  441. },
  442. filter: filter.Not[*kubecost.Allocation]{
  443. Filter: filter.StringProperty[*kubecost.Allocation]{
  444. Field: kubecost.AllocationNamespaceProp,
  445. Op: filter.StringEquals,
  446. Value: kubecost.UnallocatedSuffix,
  447. },
  448. },
  449. expected: true,
  450. },
  451. {
  452. name: "Namespace NotEquals Unallocated -> false",
  453. a: &kubecost.Allocation{
  454. Properties: &kubecost.AllocationProperties{
  455. Namespace: "",
  456. },
  457. },
  458. filter: filter.Not[*kubecost.Allocation]{
  459. Filter: filter.StringProperty[*kubecost.Allocation]{
  460. Field: kubecost.AllocationNamespaceProp,
  461. Op: filter.StringEquals,
  462. Value: kubecost.UnallocatedSuffix,
  463. },
  464. },
  465. expected: false,
  466. },
  467. {
  468. name: `label[app]!=Unallocated -> label missing -> false`,
  469. a: &kubecost.Allocation{
  470. Properties: &kubecost.AllocationProperties{
  471. Labels: map[string]string{
  472. "someotherlabel": "someothervalue",
  473. },
  474. },
  475. },
  476. filter: filter.Not[*kubecost.Allocation]{
  477. Filter: filter.StringMapProperty[*kubecost.Allocation]{
  478. Field: kubecost.AllocationLabelProp,
  479. Op: filter.StringMapEquals,
  480. Key: "app",
  481. Value: kubecost.UnallocatedSuffix,
  482. },
  483. },
  484. expected: false,
  485. },
  486. {
  487. name: `label[app]!=Unallocated -> label present -> true`,
  488. a: &kubecost.Allocation{
  489. Properties: &kubecost.AllocationProperties{
  490. Labels: map[string]string{
  491. "app": "test",
  492. },
  493. },
  494. },
  495. filter: filter.Not[*kubecost.Allocation]{
  496. Filter: filter.StringMapProperty[*kubecost.Allocation]{
  497. Field: kubecost.AllocationLabelProp,
  498. Op: filter.StringMapEquals,
  499. Key: "app",
  500. Value: kubecost.UnallocatedSuffix,
  501. },
  502. },
  503. expected: true,
  504. },
  505. {
  506. name: `label[app]!="foo" -> label missing -> true`,
  507. a: &kubecost.Allocation{
  508. Properties: &kubecost.AllocationProperties{
  509. Labels: map[string]string{
  510. "someotherlabel": "someothervalue",
  511. },
  512. },
  513. },
  514. filter: filter.Not[*kubecost.Allocation]{
  515. Filter: filter.StringMapProperty[*kubecost.Allocation]{
  516. Field: kubecost.AllocationLabelProp,
  517. Op: filter.StringMapEquals,
  518. Key: "app",
  519. Value: "foo",
  520. },
  521. },
  522. expected: true,
  523. },
  524. {
  525. name: `annotation[prom_modified_name]="testing123" -> true`,
  526. a: &kubecost.Allocation{
  527. Properties: &kubecost.AllocationProperties{
  528. Annotations: map[string]string{
  529. "prom_modified_name": "testing123",
  530. },
  531. },
  532. },
  533. filter: filter.StringMapProperty[*kubecost.Allocation]{
  534. Field: kubecost.AllocationAnnotationProp,
  535. Op: filter.StringMapEquals,
  536. Key: "prom_modified_name",
  537. Value: "testing123",
  538. },
  539. expected: true,
  540. },
  541. {
  542. name: `annotation[app]="foo" -> different value -> false`,
  543. a: &kubecost.Allocation{
  544. Properties: &kubecost.AllocationProperties{
  545. Annotations: map[string]string{
  546. "app": "bar",
  547. },
  548. },
  549. },
  550. filter: filter.StringMapProperty[*kubecost.Allocation]{
  551. Field: kubecost.AllocationAnnotationProp,
  552. Op: filter.StringMapEquals,
  553. Key: "app",
  554. Value: "foo",
  555. },
  556. expected: false,
  557. },
  558. {
  559. name: `annotation[app]="foo" -> annotation missing -> false`,
  560. a: &kubecost.Allocation{
  561. Properties: &kubecost.AllocationProperties{
  562. Annotations: map[string]string{
  563. "someotherannotation": "someothervalue",
  564. },
  565. },
  566. },
  567. filter: filter.StringMapProperty[*kubecost.Allocation]{
  568. Field: kubecost.AllocationAnnotationProp,
  569. Op: filter.StringMapEquals,
  570. Key: "app",
  571. Value: "foo",
  572. },
  573. expected: false,
  574. },
  575. {
  576. name: `annotation[app]!="foo" -> annotation missing -> true`,
  577. a: &kubecost.Allocation{
  578. Properties: &kubecost.AllocationProperties{
  579. Annotations: map[string]string{
  580. "someotherannotation": "someothervalue",
  581. },
  582. },
  583. },
  584. filter: filter.Not[*kubecost.Allocation]{
  585. Filter: filter.StringMapProperty[*kubecost.Allocation]{
  586. Field: kubecost.AllocationAnnotationProp,
  587. Op: filter.StringMapEquals,
  588. Key: "app",
  589. Value: "foo",
  590. },
  591. },
  592. expected: true,
  593. },
  594. {
  595. name: `namespace unallocated -> true`,
  596. a: &kubecost.Allocation{
  597. Properties: &kubecost.AllocationProperties{
  598. Namespace: "",
  599. },
  600. },
  601. filter: filter.StringProperty[*kubecost.Allocation]{
  602. Field: kubecost.AllocationNamespaceProp,
  603. Op: filter.StringEquals,
  604. Value: kubecost.UnallocatedSuffix,
  605. },
  606. expected: true,
  607. },
  608. {
  609. name: `services contains -> true`,
  610. a: &kubecost.Allocation{
  611. Properties: &kubecost.AllocationProperties{
  612. Services: []string{"serv1", "serv2"},
  613. },
  614. },
  615. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  616. Field: kubecost.AllocationServiceProp,
  617. Op: filter.StringSliceContains,
  618. Value: "serv2",
  619. },
  620. expected: true,
  621. },
  622. {
  623. name: `services contains -> false`,
  624. a: &kubecost.Allocation{
  625. Properties: &kubecost.AllocationProperties{
  626. Services: []string{"serv1", "serv2"},
  627. },
  628. },
  629. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  630. Field: kubecost.AllocationServiceProp,
  631. Op: filter.StringSliceContains,
  632. Value: "serv3",
  633. },
  634. expected: false,
  635. },
  636. {
  637. name: `services notcontains -> true`,
  638. a: &kubecost.Allocation{
  639. Properties: &kubecost.AllocationProperties{
  640. Services: []string{"serv1", "serv2"},
  641. },
  642. },
  643. filter: filter.Not[*kubecost.Allocation]{
  644. Filter: filter.StringSliceProperty[*kubecost.Allocation]{
  645. Field: kubecost.AllocationServiceProp,
  646. Op: filter.StringSliceContains,
  647. Value: "serv3",
  648. },
  649. },
  650. expected: true,
  651. },
  652. {
  653. name: `services notcontains -> false`,
  654. a: &kubecost.Allocation{
  655. Properties: &kubecost.AllocationProperties{
  656. Services: []string{"serv1", "serv2"},
  657. },
  658. },
  659. filter: filter.Not[*kubecost.Allocation]{
  660. Filter: filter.StringSliceProperty[*kubecost.Allocation]{
  661. Field: kubecost.AllocationServiceProp,
  662. Op: filter.StringSliceContains,
  663. Value: "serv2",
  664. },
  665. },
  666. expected: false,
  667. },
  668. {
  669. name: `services notcontains unallocated -> true`,
  670. a: &kubecost.Allocation{
  671. Properties: &kubecost.AllocationProperties{
  672. Services: []string{"serv1", "serv2"},
  673. },
  674. },
  675. filter: filter.Not[*kubecost.Allocation]{
  676. Filter: filter.StringSliceProperty[*kubecost.Allocation]{
  677. Field: kubecost.AllocationServiceProp,
  678. Op: filter.StringSliceContains,
  679. Value: kubecost.UnallocatedSuffix,
  680. },
  681. },
  682. expected: true,
  683. },
  684. {
  685. name: `services notcontains unallocated -> false`,
  686. a: &kubecost.Allocation{
  687. Properties: &kubecost.AllocationProperties{
  688. Services: []string{},
  689. },
  690. },
  691. filter: filter.Not[*kubecost.Allocation]{
  692. Filter: filter.StringSliceProperty[*kubecost.Allocation]{
  693. Field: kubecost.AllocationServiceProp,
  694. Op: filter.StringSliceContains,
  695. Value: kubecost.UnallocatedSuffix,
  696. },
  697. },
  698. expected: false,
  699. },
  700. {
  701. name: `services containsprefix -> true`,
  702. a: &kubecost.Allocation{
  703. Properties: &kubecost.AllocationProperties{
  704. Services: []string{"serv1", "serv2"},
  705. },
  706. },
  707. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  708. Field: kubecost.AllocationServiceProp,
  709. Op: filter.StringSliceContainsPrefix,
  710. Value: "serv",
  711. },
  712. expected: true,
  713. },
  714. {
  715. name: `services containsprefix -> false`,
  716. a: &kubecost.Allocation{
  717. Properties: &kubecost.AllocationProperties{
  718. Services: []string{"foo", "bar"},
  719. },
  720. },
  721. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  722. Field: kubecost.AllocationServiceProp,
  723. Op: filter.StringSliceContainsPrefix,
  724. Value: "serv",
  725. },
  726. expected: false,
  727. },
  728. {
  729. name: `services contains unallocated -> false`,
  730. a: &kubecost.Allocation{
  731. Properties: &kubecost.AllocationProperties{
  732. Services: []string{"serv1", "serv2"},
  733. },
  734. },
  735. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  736. Field: kubecost.AllocationServiceProp,
  737. Op: filter.StringSliceContains,
  738. Value: kubecost.UnallocatedSuffix,
  739. },
  740. expected: false,
  741. },
  742. {
  743. name: `services contains unallocated -> true`,
  744. a: &kubecost.Allocation{
  745. Properties: &kubecost.AllocationProperties{
  746. Services: []string{},
  747. },
  748. },
  749. filter: filter.StringSliceProperty[*kubecost.Allocation]{
  750. Field: kubecost.AllocationServiceProp,
  751. Op: filter.StringSliceContains,
  752. Value: kubecost.UnallocatedSuffix,
  753. },
  754. expected: true,
  755. },
  756. }
  757. for _, c := range cases {
  758. result := c.filter.Matches(c.a)
  759. if result != c.expected {
  760. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  761. }
  762. }
  763. }
  764. func Test_None_Matches(t *testing.T) {
  765. cases := []struct {
  766. name string
  767. a *kubecost.Allocation
  768. }{
  769. {
  770. name: "nil",
  771. a: nil,
  772. },
  773. {
  774. name: "nil properties",
  775. a: &kubecost.Allocation{
  776. Properties: nil,
  777. },
  778. },
  779. {
  780. name: "empty properties",
  781. a: &kubecost.Allocation{
  782. Properties: &kubecost.AllocationProperties{},
  783. },
  784. },
  785. {
  786. name: "ClusterID",
  787. a: &kubecost.Allocation{
  788. Properties: &kubecost.AllocationProperties{
  789. Cluster: "cluster-one",
  790. },
  791. },
  792. },
  793. {
  794. name: "Node",
  795. a: &kubecost.Allocation{
  796. Properties: &kubecost.AllocationProperties{
  797. Node: "node123",
  798. },
  799. },
  800. },
  801. {
  802. name: "Namespace",
  803. a: &kubecost.Allocation{
  804. Properties: &kubecost.AllocationProperties{
  805. Namespace: "kube-system",
  806. },
  807. },
  808. },
  809. {
  810. name: "ControllerKind",
  811. a: &kubecost.Allocation{
  812. Properties: &kubecost.AllocationProperties{
  813. ControllerKind: "deployment", // We generally store controller kinds as all lowercase
  814. },
  815. },
  816. },
  817. {
  818. name: "ControllerName",
  819. a: &kubecost.Allocation{
  820. Properties: &kubecost.AllocationProperties{
  821. Controller: "kc-cost-analyzer",
  822. },
  823. },
  824. },
  825. {
  826. name: "Pod",
  827. a: &kubecost.Allocation{
  828. Properties: &kubecost.AllocationProperties{
  829. Pod: "pod-123 UID-ABC",
  830. },
  831. },
  832. },
  833. {
  834. name: "Container",
  835. a: &kubecost.Allocation{
  836. Properties: &kubecost.AllocationProperties{
  837. Container: "cost-model",
  838. },
  839. },
  840. },
  841. {
  842. name: `label`,
  843. a: &kubecost.Allocation{
  844. Properties: &kubecost.AllocationProperties{
  845. Labels: map[string]string{
  846. "app": "foo",
  847. },
  848. },
  849. },
  850. },
  851. {
  852. name: `annotation`,
  853. a: &kubecost.Allocation{
  854. Properties: &kubecost.AllocationProperties{
  855. Annotations: map[string]string{
  856. "prom_modified_name": "testing123",
  857. },
  858. },
  859. },
  860. },
  861. {
  862. name: `services`,
  863. a: &kubecost.Allocation{
  864. Properties: &kubecost.AllocationProperties{
  865. Services: []string{"serv1", "serv2"},
  866. },
  867. },
  868. },
  869. }
  870. for _, c := range cases {
  871. result := filter.AllCut[*kubecost.Allocation]{}.Matches(c.a)
  872. if result {
  873. t.Errorf("%s: should have been rejected", c.name)
  874. }
  875. }
  876. }
  877. func Test_And_Matches(t *testing.T) {
  878. cases := []struct {
  879. name string
  880. a *kubecost.Allocation
  881. filter filter.Filter[*kubecost.Allocation]
  882. expected bool
  883. }{
  884. {
  885. name: `label[app]="foo" and namespace="kubecost" -> both true`,
  886. a: &kubecost.Allocation{
  887. Properties: &kubecost.AllocationProperties{
  888. Namespace: "kubecost",
  889. Labels: map[string]string{
  890. "app": "foo",
  891. },
  892. },
  893. },
  894. filter: filter.And[*kubecost.Allocation]{[]filter.Filter[*kubecost.Allocation]{
  895. filter.StringMapProperty[*kubecost.Allocation]{
  896. Field: kubecost.AllocationLabelProp,
  897. Op: filter.StringMapEquals,
  898. Key: "app",
  899. Value: "foo",
  900. },
  901. filter.StringProperty[*kubecost.Allocation]{
  902. Field: kubecost.AllocationNamespaceProp,
  903. Op: filter.StringEquals,
  904. Value: "kubecost",
  905. },
  906. }},
  907. expected: true,
  908. },
  909. {
  910. name: `label[app]="foo" and namespace="kubecost" -> first true`,
  911. a: &kubecost.Allocation{
  912. Properties: &kubecost.AllocationProperties{
  913. Namespace: "kube-system",
  914. Labels: map[string]string{
  915. "app": "foo",
  916. },
  917. },
  918. },
  919. filter: filter.And[*kubecost.Allocation]{[]filter.Filter[*kubecost.Allocation]{
  920. filter.StringMapProperty[*kubecost.Allocation]{
  921. Field: kubecost.AllocationLabelProp,
  922. Op: filter.StringMapEquals,
  923. Key: "app",
  924. Value: "foo",
  925. },
  926. filter.StringProperty[*kubecost.Allocation]{
  927. Field: kubecost.AllocationNamespaceProp,
  928. Op: filter.StringEquals,
  929. Value: "kubecost",
  930. },
  931. }},
  932. expected: false,
  933. },
  934. {
  935. name: `label[app]="foo" and namespace="kubecost" -> second true`,
  936. a: &kubecost.Allocation{
  937. Properties: &kubecost.AllocationProperties{
  938. Namespace: "kubecost",
  939. Labels: map[string]string{
  940. "app": "bar",
  941. },
  942. },
  943. },
  944. filter: filter.And[*kubecost.Allocation]{[]filter.Filter[*kubecost.Allocation]{
  945. filter.StringMapProperty[*kubecost.Allocation]{
  946. Field: kubecost.AllocationLabelProp,
  947. Op: filter.StringMapEquals,
  948. Key: "app",
  949. Value: "foo",
  950. },
  951. filter.StringProperty[*kubecost.Allocation]{
  952. Field: kubecost.AllocationNamespaceProp,
  953. Op: filter.StringEquals,
  954. Value: "kubecost",
  955. },
  956. }},
  957. expected: false,
  958. },
  959. {
  960. name: `label[app]="foo" and namespace="kubecost" -> both false`,
  961. a: &kubecost.Allocation{
  962. Properties: &kubecost.AllocationProperties{
  963. Namespace: "kube-system",
  964. Labels: map[string]string{
  965. "app": "bar",
  966. },
  967. },
  968. },
  969. filter: filter.And[*kubecost.Allocation]{[]filter.Filter[*kubecost.Allocation]{
  970. filter.StringMapProperty[*kubecost.Allocation]{
  971. Field: kubecost.AllocationLabelProp,
  972. Op: filter.StringMapEquals,
  973. Key: "app",
  974. Value: "foo",
  975. },
  976. filter.StringProperty[*kubecost.Allocation]{
  977. Field: kubecost.AllocationNamespaceProp,
  978. Op: filter.StringEquals,
  979. Value: "kubecost",
  980. },
  981. }},
  982. expected: false,
  983. },
  984. {
  985. name: `(and none) matches nothing`,
  986. a: &kubecost.Allocation{
  987. Properties: &kubecost.AllocationProperties{
  988. Namespace: "kube-system",
  989. Labels: map[string]string{
  990. "app": "bar",
  991. },
  992. },
  993. },
  994. filter: filter.And[*kubecost.Allocation]{[]filter.Filter[*kubecost.Allocation]{
  995. filter.AllCut[*kubecost.Allocation]{},
  996. }},
  997. expected: false,
  998. },
  999. }
  1000. for _, c := range cases {
  1001. result := c.filter.Matches(c.a)
  1002. if result != c.expected {
  1003. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  1004. }
  1005. }
  1006. }