allocationfilter_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. package kubecost
  2. import (
  3. "testing"
  4. filter21 "github.com/opencost/opencost/pkg/filter21"
  5. afilter "github.com/opencost/opencost/pkg/filter21/allocation"
  6. "github.com/opencost/opencost/pkg/filter21/ast"
  7. "github.com/opencost/opencost/pkg/filter21/ops"
  8. )
  9. func Test_AllocationFilterCondition_Matches(t *testing.T) {
  10. labelConfig := &LabelConfig{
  11. DepartmentLabel: "keydepartment",
  12. EnvironmentLabel: "keyenvironment",
  13. OwnerLabel: "keyowner",
  14. ProductLabel: "keyproduct",
  15. TeamLabel: "keyteam",
  16. }
  17. cases := []struct {
  18. name string
  19. a *Allocation
  20. filter filter21.Filter
  21. expected bool
  22. }{
  23. {
  24. name: "ClusterID Equals -> true",
  25. a: &Allocation{
  26. Properties: &AllocationProperties{
  27. Cluster: "cluster-one",
  28. },
  29. },
  30. filter: ops.Eq(afilter.FieldClusterID, "cluster-one"),
  31. expected: true,
  32. },
  33. {
  34. name: "ClusterID StartsWith -> true",
  35. a: &Allocation{
  36. Properties: &AllocationProperties{
  37. Cluster: "cluster-one",
  38. },
  39. },
  40. filter: ops.ContainsPrefix(afilter.FieldClusterID, "cluster"),
  41. expected: true,
  42. },
  43. {
  44. name: "ClusterID StartsWith -> false",
  45. a: &Allocation{
  46. Properties: &AllocationProperties{
  47. Cluster: "k8s-one",
  48. },
  49. },
  50. filter: ops.ContainsPrefix(afilter.FieldClusterID, "cluster"),
  51. expected: false,
  52. },
  53. {
  54. name: "ClusterID empty StartsWith '' -> true",
  55. a: &Allocation{
  56. Properties: &AllocationProperties{
  57. Cluster: "",
  58. },
  59. },
  60. filter: ops.ContainsPrefix(afilter.FieldClusterID, ""),
  61. expected: true,
  62. },
  63. {
  64. name: "ClusterID nonempty StartsWith '' -> true",
  65. a: &Allocation{
  66. Properties: &AllocationProperties{
  67. Cluster: "abc",
  68. },
  69. },
  70. filter: ops.ContainsPrefix(afilter.FieldClusterID, ""),
  71. expected: true,
  72. },
  73. {
  74. name: "Node Equals -> true",
  75. a: &Allocation{
  76. Properties: &AllocationProperties{
  77. Node: "node123",
  78. },
  79. },
  80. filter: ops.Eq(afilter.FieldNode, "node123"),
  81. expected: true,
  82. },
  83. {
  84. name: "Namespace NotEquals -> false",
  85. a: &Allocation{
  86. Properties: &AllocationProperties{
  87. Namespace: "kube-system",
  88. },
  89. },
  90. filter: ops.NotEq(afilter.FieldNamespace, "kube-system"),
  91. expected: false,
  92. },
  93. {
  94. name: "Namespace NotEquals Unallocated -> true",
  95. a: &Allocation{
  96. Properties: &AllocationProperties{
  97. Namespace: "kube-system",
  98. },
  99. },
  100. filter: ops.NotEq(afilter.FieldNamespace, UnallocatedSuffix),
  101. expected: true,
  102. },
  103. {
  104. name: "Namespace NotEquals Unallocated -> false",
  105. a: &Allocation{
  106. Properties: &AllocationProperties{
  107. Namespace: "",
  108. },
  109. },
  110. filter: ops.NotEq(afilter.FieldNamespace, UnallocatedSuffix),
  111. expected: false,
  112. },
  113. {
  114. name: "Namespace Equals Unallocated -> true",
  115. a: &Allocation{
  116. Properties: &AllocationProperties{
  117. Namespace: "",
  118. },
  119. },
  120. filter: ops.Eq(afilter.FieldNamespace, UnallocatedSuffix),
  121. expected: true,
  122. },
  123. {
  124. name: "ControllerKind Equals -> true",
  125. a: &Allocation{
  126. Properties: &AllocationProperties{
  127. ControllerKind: "deployment", // We generally store controller kinds as all lowercase
  128. },
  129. },
  130. filter: ops.Eq(afilter.FieldControllerKind, "deployment"),
  131. expected: true,
  132. },
  133. {
  134. name: "ControllerName Equals -> true",
  135. a: &Allocation{
  136. Properties: &AllocationProperties{
  137. Controller: "kc-cost-analyzer",
  138. },
  139. },
  140. filter: ops.Eq(afilter.FieldControllerName, "kc-cost-analyzer"),
  141. expected: true,
  142. },
  143. {
  144. name: "Pod (with UID) Equals -> true",
  145. a: &Allocation{
  146. Properties: &AllocationProperties{
  147. Pod: "pod-123 UID-ABC",
  148. },
  149. },
  150. filter: ops.Eq(afilter.FieldPod, "pod-123 UID-ABC"),
  151. expected: true,
  152. },
  153. {
  154. name: "Container Equals -> true",
  155. a: &Allocation{
  156. Properties: &AllocationProperties{
  157. Container: "cost-model",
  158. },
  159. },
  160. filter: ops.Eq(afilter.FieldContainer, "cost-model"),
  161. expected: true,
  162. },
  163. {
  164. name: `label[app]="foo" -> true`,
  165. a: &Allocation{
  166. Properties: &AllocationProperties{
  167. Labels: map[string]string{
  168. "app": "foo",
  169. },
  170. },
  171. },
  172. filter: ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  173. expected: true,
  174. },
  175. {
  176. name: `label[app]="foo" -> different value -> false`,
  177. a: &Allocation{
  178. Properties: &AllocationProperties{
  179. Labels: map[string]string{
  180. "app": "bar",
  181. },
  182. },
  183. },
  184. filter: ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  185. expected: false,
  186. },
  187. {
  188. name: `label[app]="foo" -> label missing -> false`,
  189. a: &Allocation{
  190. Properties: &AllocationProperties{
  191. Labels: map[string]string{
  192. "someotherlabel": "someothervalue",
  193. },
  194. },
  195. },
  196. filter: ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  197. expected: false,
  198. },
  199. {
  200. name: `label[app]=Unallocated -> label missing -> true`,
  201. a: &Allocation{
  202. Properties: &AllocationProperties{
  203. Labels: map[string]string{
  204. "someotherlabel": "someothervalue",
  205. },
  206. },
  207. },
  208. filter: ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), UnallocatedSuffix),
  209. expected: true,
  210. },
  211. {
  212. name: `label[app]=Unallocated -> label present -> false`,
  213. a: &Allocation{
  214. Properties: &AllocationProperties{
  215. Labels: map[string]string{
  216. "app": "test",
  217. },
  218. },
  219. },
  220. filter: ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), UnallocatedSuffix),
  221. expected: false,
  222. },
  223. {
  224. name: `label[app]!=Unallocated -> label missing -> false`,
  225. a: &Allocation{
  226. Properties: &AllocationProperties{
  227. Labels: map[string]string{
  228. "someotherlabel": "someothervalue",
  229. },
  230. },
  231. },
  232. filter: ops.NotEq(ops.WithKey(afilter.FieldLabel, "app"), UnallocatedSuffix),
  233. expected: false,
  234. },
  235. {
  236. name: `label[app]!=Unallocated -> label present -> true`,
  237. a: &Allocation{
  238. Properties: &AllocationProperties{
  239. Labels: map[string]string{
  240. "app": "test",
  241. },
  242. },
  243. },
  244. filter: ops.NotEq(ops.WithKey(afilter.FieldLabel, "app"), UnallocatedSuffix),
  245. expected: true,
  246. },
  247. {
  248. name: `label[app]!="foo" -> label missing -> true`,
  249. a: &Allocation{
  250. Properties: &AllocationProperties{
  251. Labels: map[string]string{
  252. "someotherlabel": "someothervalue",
  253. },
  254. },
  255. },
  256. filter: ops.NotEq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  257. expected: true,
  258. },
  259. {
  260. name: `annotation[prom_modified_name]="testing123" -> true`,
  261. a: &Allocation{
  262. Properties: &AllocationProperties{
  263. Annotations: map[string]string{
  264. "prom_modified_name": "testing123",
  265. },
  266. },
  267. },
  268. filter: ops.Eq(ops.WithKey(afilter.FieldAnnotation, "prom_modified_name"), "testing123"),
  269. expected: true,
  270. },
  271. {
  272. name: `annotation[app]="foo" -> different value -> false`,
  273. a: &Allocation{
  274. Properties: &AllocationProperties{
  275. Annotations: map[string]string{
  276. "app": "bar",
  277. },
  278. },
  279. },
  280. filter: ops.Eq(ops.WithKey(afilter.FieldAnnotation, "app"), "foo"),
  281. expected: false,
  282. },
  283. {
  284. name: `annotation[app]="foo" -> annotation missing -> false`,
  285. a: &Allocation{
  286. Properties: &AllocationProperties{
  287. Annotations: map[string]string{
  288. "someotherannotation": "someothervalue",
  289. },
  290. },
  291. },
  292. filter: ops.Eq(ops.WithKey(afilter.FieldAnnotation, "app"), "foo"),
  293. expected: false,
  294. },
  295. {
  296. name: `annotation[app]!="foo" -> annotation missing -> true`,
  297. a: &Allocation{
  298. Properties: &AllocationProperties{
  299. Annotations: map[string]string{
  300. "someotherannotation": "someothervalue",
  301. },
  302. },
  303. },
  304. filter: ops.NotEq(ops.WithKey(afilter.FieldAnnotation, "app"), "foo"),
  305. expected: true,
  306. },
  307. {
  308. name: `namespace unallocated -> true`,
  309. a: &Allocation{
  310. Properties: &AllocationProperties{
  311. Namespace: "",
  312. },
  313. },
  314. filter: ops.Eq(afilter.FieldNamespace, UnallocatedSuffix),
  315. expected: true,
  316. },
  317. {
  318. name: `services contains -> true`,
  319. a: &Allocation{
  320. Properties: &AllocationProperties{
  321. Services: []string{"serv1", "serv2"},
  322. },
  323. },
  324. filter: ops.Contains(afilter.FieldServices, "serv2"),
  325. expected: true,
  326. },
  327. {
  328. name: `services contains -> false`,
  329. a: &Allocation{
  330. Properties: &AllocationProperties{
  331. Services: []string{"serv1", "serv2"},
  332. },
  333. },
  334. filter: ops.Contains(afilter.FieldServices, "serv3"),
  335. expected: false,
  336. },
  337. {
  338. name: `services notcontains -> true`,
  339. a: &Allocation{
  340. Properties: &AllocationProperties{
  341. Services: []string{"serv1", "serv2"},
  342. },
  343. },
  344. filter: ops.NotContains(afilter.FieldServices, "serv3"),
  345. expected: true,
  346. },
  347. {
  348. name: `services notcontains -> false`,
  349. a: &Allocation{
  350. Properties: &AllocationProperties{
  351. Services: []string{"serv1", "serv2"},
  352. },
  353. },
  354. filter: ops.NotContains(afilter.FieldServices, "serv2"),
  355. expected: false,
  356. },
  357. {
  358. name: `services notcontains unallocated -> true`,
  359. a: &Allocation{
  360. Properties: &AllocationProperties{
  361. Services: []string{"serv1", "serv2"},
  362. },
  363. },
  364. filter: ops.NotContains(afilter.FieldServices, UnallocatedSuffix),
  365. expected: true,
  366. },
  367. {
  368. name: `services notcontains unallocated -> false`,
  369. a: &Allocation{
  370. Properties: &AllocationProperties{
  371. Services: []string{},
  372. },
  373. },
  374. filter: ops.NotContains(afilter.FieldServices, UnallocatedSuffix),
  375. expected: false,
  376. },
  377. {
  378. name: `services containsprefix -> true`,
  379. a: &Allocation{
  380. Properties: &AllocationProperties{
  381. Services: []string{"serv1", "serv2"},
  382. },
  383. },
  384. filter: ops.ContainsPrefix(afilter.FieldServices, "serv"),
  385. expected: true,
  386. },
  387. {
  388. name: `services containsprefix -> false`,
  389. a: &Allocation{
  390. Properties: &AllocationProperties{
  391. Services: []string{"foo", "bar"},
  392. },
  393. },
  394. filter: ops.ContainsPrefix(afilter.FieldServices, "serv"),
  395. expected: false,
  396. },
  397. {
  398. name: `services contains unallocated -> false`,
  399. a: &Allocation{
  400. Properties: &AllocationProperties{
  401. Services: []string{"serv1", "serv2"},
  402. },
  403. },
  404. filter: ops.Contains(afilter.FieldServices, UnallocatedSuffix),
  405. expected: false,
  406. },
  407. {
  408. name: `services contains unallocated -> true`,
  409. a: &Allocation{
  410. Properties: &AllocationProperties{
  411. Services: []string{},
  412. },
  413. },
  414. filter: ops.Contains(afilter.FieldServices, UnallocatedSuffix),
  415. expected: true,
  416. },
  417. {
  418. name: `department equals -> true`,
  419. a: &Allocation{
  420. Properties: &AllocationProperties{
  421. Labels: AllocationLabels{
  422. "keydepartment": "foo",
  423. },
  424. },
  425. },
  426. // The ops package doesn't handle alias construction quite right,
  427. // so we construct it more manually here
  428. filter: &ast.EqualOp{
  429. Left: ast.Identifier{
  430. Field: ast.NewAliasField(afilter.AliasDepartment),
  431. },
  432. Right: "foo",
  433. },
  434. expected: true,
  435. },
  436. {
  437. name: `department != unallocated -> true`,
  438. a: &Allocation{
  439. Properties: &AllocationProperties{
  440. Annotations: AllocationAnnotations{
  441. "keydepartment": "foo",
  442. },
  443. },
  444. },
  445. // The ops package doesn't handle alias construction quite right,
  446. // so we construct it more manually here
  447. filter: &ast.NotOp{
  448. Operand: &ast.EqualOp{
  449. Left: ast.Identifier{
  450. Field: ast.NewAliasField(afilter.AliasDepartment),
  451. },
  452. Right: UnallocatedSuffix,
  453. },
  454. },
  455. expected: true,
  456. },
  457. {
  458. name: `product == unallocated -> true`,
  459. a: &Allocation{
  460. Properties: &AllocationProperties{
  461. Annotations: AllocationAnnotations{
  462. "keydepartment": "foo",
  463. },
  464. },
  465. },
  466. filter: &ast.EqualOp{
  467. Left: ast.Identifier{
  468. Field: ast.NewAliasField(afilter.AliasProduct),
  469. },
  470. Right: UnallocatedSuffix,
  471. },
  472. expected: true,
  473. },
  474. {
  475. name: `product == "" -> true`,
  476. a: &Allocation{
  477. Properties: &AllocationProperties{
  478. Labels: AllocationLabels{
  479. "keydepartment": "foo",
  480. },
  481. Annotations: AllocationAnnotations{
  482. "keyowner": "bar",
  483. },
  484. },
  485. },
  486. filter: &ast.EqualOp{
  487. Left: ast.Identifier{
  488. Field: ast.NewAliasField(afilter.AliasProduct),
  489. },
  490. Right: "",
  491. },
  492. expected: true,
  493. },
  494. }
  495. for _, c := range cases {
  496. compiler := NewAllocationMatchCompiler(labelConfig)
  497. compiled, err := compiler.Compile(c.filter)
  498. if err != nil {
  499. t.Fatalf("err compiling filter '%s': %s", ast.ToPreOrderShortString(c.filter), err)
  500. }
  501. result := compiled.Matches(c.a)
  502. if result != c.expected {
  503. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  504. }
  505. }
  506. }
  507. func Test_AllocationFilterContradiction_Matches(t *testing.T) {
  508. cases := []struct {
  509. name string
  510. a *Allocation
  511. }{
  512. {
  513. name: "nil",
  514. a: nil,
  515. },
  516. {
  517. name: "nil properties",
  518. a: &Allocation{
  519. Properties: nil,
  520. },
  521. },
  522. {
  523. name: "empty properties",
  524. a: &Allocation{
  525. Properties: &AllocationProperties{},
  526. },
  527. },
  528. {
  529. name: "ClusterID",
  530. a: &Allocation{
  531. Properties: &AllocationProperties{
  532. Cluster: "cluster-one",
  533. },
  534. },
  535. },
  536. {
  537. name: "Node",
  538. a: &Allocation{
  539. Properties: &AllocationProperties{
  540. Node: "node123",
  541. },
  542. },
  543. },
  544. {
  545. name: "Namespace",
  546. a: &Allocation{
  547. Properties: &AllocationProperties{
  548. Namespace: "kube-system",
  549. },
  550. },
  551. },
  552. {
  553. name: "ControllerKind",
  554. a: &Allocation{
  555. Properties: &AllocationProperties{
  556. ControllerKind: "deployment", // We generally store controller kinds as all lowercase
  557. },
  558. },
  559. },
  560. {
  561. name: "ControllerName",
  562. a: &Allocation{
  563. Properties: &AllocationProperties{
  564. Controller: "kc-cost-analyzer",
  565. },
  566. },
  567. },
  568. {
  569. name: "Pod",
  570. a: &Allocation{
  571. Properties: &AllocationProperties{
  572. Pod: "pod-123 UID-ABC",
  573. },
  574. },
  575. },
  576. {
  577. name: "Container",
  578. a: &Allocation{
  579. Properties: &AllocationProperties{
  580. Container: "cost-model",
  581. },
  582. },
  583. },
  584. {
  585. name: `label`,
  586. a: &Allocation{
  587. Properties: &AllocationProperties{
  588. Labels: map[string]string{
  589. "app": "foo",
  590. },
  591. },
  592. },
  593. },
  594. {
  595. name: `annotation`,
  596. a: &Allocation{
  597. Properties: &AllocationProperties{
  598. Annotations: map[string]string{
  599. "prom_modified_name": "testing123",
  600. },
  601. },
  602. },
  603. },
  604. {
  605. name: `services`,
  606. a: &Allocation{
  607. Properties: &AllocationProperties{
  608. Services: []string{"serv1", "serv2"},
  609. },
  610. },
  611. },
  612. }
  613. for _, c := range cases {
  614. filter := &ast.ContradictionOp{}
  615. compiler := NewAllocationMatchCompiler(nil)
  616. compiled, err := compiler.Compile(filter)
  617. if err != nil {
  618. t.Fatalf("err compiling filter '%s': %s", ast.ToPreOrderShortString(filter), err)
  619. }
  620. result := compiled.Matches(c.a)
  621. if result {
  622. t.Errorf("%s: should have been rejected", c.name)
  623. }
  624. }
  625. }
  626. func Test_AllocationFilterAnd_Matches(t *testing.T) {
  627. cases := []struct {
  628. name string
  629. a *Allocation
  630. filter filter21.Filter
  631. expected bool
  632. }{
  633. {
  634. name: `label[app]="foo" and namespace="kubecost" -> both true`,
  635. a: &Allocation{
  636. Properties: &AllocationProperties{
  637. Namespace: "kubecost",
  638. Labels: map[string]string{
  639. "app": "foo",
  640. },
  641. },
  642. },
  643. filter: ops.And(
  644. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  645. ops.Eq(afilter.FieldNamespace, "kubecost"),
  646. ),
  647. expected: true,
  648. },
  649. {
  650. name: `label[app]="foo" and namespace="kubecost" -> first true`,
  651. a: &Allocation{
  652. Properties: &AllocationProperties{
  653. Namespace: "kube-system",
  654. Labels: map[string]string{
  655. "app": "foo",
  656. },
  657. },
  658. },
  659. filter: ops.And(
  660. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  661. ops.Eq(afilter.FieldNamespace, "kubecost"),
  662. ),
  663. expected: false,
  664. },
  665. {
  666. name: `label[app]="foo" and namespace="kubecost" -> second true`,
  667. a: &Allocation{
  668. Properties: &AllocationProperties{
  669. Namespace: "kubecost",
  670. Labels: map[string]string{
  671. "app": "bar",
  672. },
  673. },
  674. },
  675. filter: ops.And(
  676. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  677. ops.Eq(afilter.FieldNamespace, "kubecost"),
  678. ),
  679. expected: false,
  680. },
  681. {
  682. name: `label[app]="foo" and namespace="kubecost" -> both false`,
  683. a: &Allocation{
  684. Properties: &AllocationProperties{
  685. Namespace: "kube-system",
  686. Labels: map[string]string{
  687. "app": "bar",
  688. },
  689. },
  690. },
  691. filter: ops.And(
  692. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  693. ops.Eq(afilter.FieldNamespace, "kubecost"),
  694. ),
  695. expected: false,
  696. },
  697. {
  698. name: `contradiction matches nothing`,
  699. a: &Allocation{
  700. Properties: &AllocationProperties{
  701. Namespace: "kube-system",
  702. Labels: map[string]string{
  703. "app": "bar",
  704. },
  705. },
  706. },
  707. filter: &ast.ContradictionOp{},
  708. expected: false,
  709. },
  710. }
  711. for _, c := range cases {
  712. compiler := NewAllocationMatchCompiler(nil)
  713. compiled, err := compiler.Compile(c.filter)
  714. if err != nil {
  715. t.Fatalf("err compiling filter '%s': %s", ast.ToPreOrderShortString(c.filter), err)
  716. }
  717. result := compiled.Matches(c.a)
  718. if result != c.expected {
  719. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  720. }
  721. }
  722. }
  723. func Test_AllocationFilterOr_Matches(t *testing.T) {
  724. cases := []struct {
  725. name string
  726. a *Allocation
  727. filter filter21.Filter
  728. expected bool
  729. }{
  730. {
  731. name: `label[app]="foo" or namespace="kubecost" -> both true`,
  732. a: &Allocation{
  733. Properties: &AllocationProperties{
  734. Namespace: "kubecost",
  735. Labels: map[string]string{
  736. "app": "foo",
  737. },
  738. },
  739. },
  740. filter: ops.Or(
  741. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  742. ops.Eq(afilter.FieldNamespace, "kubecost"),
  743. ),
  744. expected: true,
  745. },
  746. {
  747. name: `label[app]="foo" or namespace="kubecost" -> first true`,
  748. a: &Allocation{
  749. Properties: &AllocationProperties{
  750. Namespace: "kube-system",
  751. Labels: map[string]string{
  752. "app": "foo",
  753. },
  754. },
  755. },
  756. filter: ops.Or(
  757. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  758. ops.Eq(afilter.FieldNamespace, "kubecost"),
  759. ),
  760. expected: true,
  761. },
  762. {
  763. name: `label[app]="foo" or namespace="kubecost" -> second true`,
  764. a: &Allocation{
  765. Properties: &AllocationProperties{
  766. Namespace: "kubecost",
  767. Labels: map[string]string{
  768. "app": "bar",
  769. },
  770. },
  771. },
  772. filter: ops.Or(
  773. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  774. ops.Eq(afilter.FieldNamespace, "kubecost"),
  775. ),
  776. expected: true,
  777. },
  778. {
  779. name: `label[app]="foo" or namespace="kubecost" -> both false`,
  780. a: &Allocation{
  781. Properties: &AllocationProperties{
  782. Namespace: "kube-system",
  783. Labels: map[string]string{
  784. "app": "bar",
  785. },
  786. },
  787. },
  788. filter: ops.Or(
  789. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  790. ops.Eq(afilter.FieldNamespace, "kubecost"),
  791. ),
  792. expected: false,
  793. },
  794. }
  795. for _, c := range cases {
  796. compiler := NewAllocationMatchCompiler(nil)
  797. compiled, err := compiler.Compile(c.filter)
  798. if err != nil {
  799. t.Fatalf("err compiling filter '%s': %s", ast.ToPreOrderShortString(c.filter), err)
  800. }
  801. result := compiled.Matches(c.a)
  802. if result != c.expected {
  803. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  804. }
  805. }
  806. }