allocationfilter_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  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: `product != unallocated -> true`,
  438. a: &Allocation{
  439. Properties: &AllocationProperties{
  440. Annotations: AllocationAnnotations{
  441. "keyproduct": "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. for _, c := range cases {
  459. compiler := NewAllocationMatchCompiler(labelConfig)
  460. compiled, err := compiler.Compile(c.filter)
  461. if err != nil {
  462. t.Fatalf("err compiling filter '%s': %s", ast.ToPreOrderShortString(c.filter), err)
  463. }
  464. result := compiled.Matches(c.a)
  465. if result != c.expected {
  466. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  467. }
  468. }
  469. }
  470. func Test_AllocationFilterContradiction_Matches(t *testing.T) {
  471. cases := []struct {
  472. name string
  473. a *Allocation
  474. }{
  475. {
  476. name: "nil",
  477. a: nil,
  478. },
  479. {
  480. name: "nil properties",
  481. a: &Allocation{
  482. Properties: nil,
  483. },
  484. },
  485. {
  486. name: "empty properties",
  487. a: &Allocation{
  488. Properties: &AllocationProperties{},
  489. },
  490. },
  491. {
  492. name: "ClusterID",
  493. a: &Allocation{
  494. Properties: &AllocationProperties{
  495. Cluster: "cluster-one",
  496. },
  497. },
  498. },
  499. {
  500. name: "Node",
  501. a: &Allocation{
  502. Properties: &AllocationProperties{
  503. Node: "node123",
  504. },
  505. },
  506. },
  507. {
  508. name: "Namespace",
  509. a: &Allocation{
  510. Properties: &AllocationProperties{
  511. Namespace: "kube-system",
  512. },
  513. },
  514. },
  515. {
  516. name: "ControllerKind",
  517. a: &Allocation{
  518. Properties: &AllocationProperties{
  519. ControllerKind: "deployment", // We generally store controller kinds as all lowercase
  520. },
  521. },
  522. },
  523. {
  524. name: "ControllerName",
  525. a: &Allocation{
  526. Properties: &AllocationProperties{
  527. Controller: "kc-cost-analyzer",
  528. },
  529. },
  530. },
  531. {
  532. name: "Pod",
  533. a: &Allocation{
  534. Properties: &AllocationProperties{
  535. Pod: "pod-123 UID-ABC",
  536. },
  537. },
  538. },
  539. {
  540. name: "Container",
  541. a: &Allocation{
  542. Properties: &AllocationProperties{
  543. Container: "cost-model",
  544. },
  545. },
  546. },
  547. {
  548. name: `label`,
  549. a: &Allocation{
  550. Properties: &AllocationProperties{
  551. Labels: map[string]string{
  552. "app": "foo",
  553. },
  554. },
  555. },
  556. },
  557. {
  558. name: `annotation`,
  559. a: &Allocation{
  560. Properties: &AllocationProperties{
  561. Annotations: map[string]string{
  562. "prom_modified_name": "testing123",
  563. },
  564. },
  565. },
  566. },
  567. {
  568. name: `services`,
  569. a: &Allocation{
  570. Properties: &AllocationProperties{
  571. Services: []string{"serv1", "serv2"},
  572. },
  573. },
  574. },
  575. }
  576. for _, c := range cases {
  577. filter := &ast.ContradictionOp{}
  578. compiler := NewAllocationMatchCompiler(nil)
  579. compiled, err := compiler.Compile(filter)
  580. if err != nil {
  581. t.Fatalf("err compiling filter '%s': %s", ast.ToPreOrderShortString(filter), err)
  582. }
  583. result := compiled.Matches(c.a)
  584. if result {
  585. t.Errorf("%s: should have been rejected", c.name)
  586. }
  587. }
  588. }
  589. func Test_AllocationFilterAnd_Matches(t *testing.T) {
  590. cases := []struct {
  591. name string
  592. a *Allocation
  593. filter filter21.Filter
  594. expected bool
  595. }{
  596. {
  597. name: `label[app]="foo" and namespace="kubecost" -> both true`,
  598. a: &Allocation{
  599. Properties: &AllocationProperties{
  600. Namespace: "kubecost",
  601. Labels: map[string]string{
  602. "app": "foo",
  603. },
  604. },
  605. },
  606. filter: ops.And(
  607. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  608. ops.Eq(afilter.FieldNamespace, "kubecost"),
  609. ),
  610. expected: true,
  611. },
  612. {
  613. name: `label[app]="foo" and namespace="kubecost" -> first true`,
  614. a: &Allocation{
  615. Properties: &AllocationProperties{
  616. Namespace: "kube-system",
  617. Labels: map[string]string{
  618. "app": "foo",
  619. },
  620. },
  621. },
  622. filter: ops.And(
  623. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  624. ops.Eq(afilter.FieldNamespace, "kubecost"),
  625. ),
  626. expected: false,
  627. },
  628. {
  629. name: `label[app]="foo" and namespace="kubecost" -> second true`,
  630. a: &Allocation{
  631. Properties: &AllocationProperties{
  632. Namespace: "kubecost",
  633. Labels: map[string]string{
  634. "app": "bar",
  635. },
  636. },
  637. },
  638. filter: ops.And(
  639. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  640. ops.Eq(afilter.FieldNamespace, "kubecost"),
  641. ),
  642. expected: false,
  643. },
  644. {
  645. name: `label[app]="foo" and namespace="kubecost" -> both false`,
  646. a: &Allocation{
  647. Properties: &AllocationProperties{
  648. Namespace: "kube-system",
  649. Labels: map[string]string{
  650. "app": "bar",
  651. },
  652. },
  653. },
  654. filter: ops.And(
  655. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  656. ops.Eq(afilter.FieldNamespace, "kubecost"),
  657. ),
  658. expected: false,
  659. },
  660. {
  661. name: `contradiction matches nothing`,
  662. a: &Allocation{
  663. Properties: &AllocationProperties{
  664. Namespace: "kube-system",
  665. Labels: map[string]string{
  666. "app": "bar",
  667. },
  668. },
  669. },
  670. filter: &ast.ContradictionOp{},
  671. expected: false,
  672. },
  673. }
  674. for _, c := range cases {
  675. compiler := NewAllocationMatchCompiler(nil)
  676. compiled, err := compiler.Compile(c.filter)
  677. if err != nil {
  678. t.Fatalf("err compiling filter '%s': %s", ast.ToPreOrderShortString(c.filter), err)
  679. }
  680. result := compiled.Matches(c.a)
  681. if result != c.expected {
  682. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  683. }
  684. }
  685. }
  686. func Test_AllocationFilterOr_Matches(t *testing.T) {
  687. cases := []struct {
  688. name string
  689. a *Allocation
  690. filter filter21.Filter
  691. expected bool
  692. }{
  693. {
  694. name: `label[app]="foo" or namespace="kubecost" -> both true`,
  695. a: &Allocation{
  696. Properties: &AllocationProperties{
  697. Namespace: "kubecost",
  698. Labels: map[string]string{
  699. "app": "foo",
  700. },
  701. },
  702. },
  703. filter: ops.Or(
  704. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  705. ops.Eq(afilter.FieldNamespace, "kubecost"),
  706. ),
  707. expected: true,
  708. },
  709. {
  710. name: `label[app]="foo" or namespace="kubecost" -> first true`,
  711. a: &Allocation{
  712. Properties: &AllocationProperties{
  713. Namespace: "kube-system",
  714. Labels: map[string]string{
  715. "app": "foo",
  716. },
  717. },
  718. },
  719. filter: ops.Or(
  720. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  721. ops.Eq(afilter.FieldNamespace, "kubecost"),
  722. ),
  723. expected: true,
  724. },
  725. {
  726. name: `label[app]="foo" or namespace="kubecost" -> second true`,
  727. a: &Allocation{
  728. Properties: &AllocationProperties{
  729. Namespace: "kubecost",
  730. Labels: map[string]string{
  731. "app": "bar",
  732. },
  733. },
  734. },
  735. filter: ops.Or(
  736. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  737. ops.Eq(afilter.FieldNamespace, "kubecost"),
  738. ),
  739. expected: true,
  740. },
  741. {
  742. name: `label[app]="foo" or namespace="kubecost" -> both false`,
  743. a: &Allocation{
  744. Properties: &AllocationProperties{
  745. Namespace: "kube-system",
  746. Labels: map[string]string{
  747. "app": "bar",
  748. },
  749. },
  750. },
  751. filter: ops.Or(
  752. ops.Eq(ops.WithKey(afilter.FieldLabel, "app"), "foo"),
  753. ops.Eq(afilter.FieldNamespace, "kubecost"),
  754. ),
  755. expected: false,
  756. },
  757. }
  758. for _, c := range cases {
  759. compiler := NewAllocationMatchCompiler(nil)
  760. compiled, err := compiler.Compile(c.filter)
  761. if err != nil {
  762. t.Fatalf("err compiling filter '%s': %s", ast.ToPreOrderShortString(c.filter), err)
  763. }
  764. result := compiled.Matches(c.a)
  765. if result != c.expected {
  766. t.Errorf("%s: expected %t, got %t", c.name, c.expected, result)
  767. }
  768. }
  769. }