2
0

allocationprops_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package opencost
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. func TestAllocationPropsIntersection(t *testing.T) {
  7. cases := map[string]struct {
  8. allocationProps1 *AllocationProperties
  9. allocationProps2 *AllocationProperties
  10. expected *AllocationProperties
  11. }{
  12. "intersection two allocation properties with empty labels/annotations": {
  13. allocationProps1: &AllocationProperties{
  14. Labels: map[string]string{},
  15. Annotations: map[string]string{},
  16. },
  17. allocationProps2: &AllocationProperties{
  18. Labels: map[string]string{},
  19. Annotations: map[string]string{},
  20. },
  21. expected: &AllocationProperties{
  22. Labels: nil,
  23. Annotations: nil,
  24. NamespaceLabels: map[string]string{},
  25. NamespaceAnnotations: map[string]string{},
  26. },
  27. },
  28. "nil intersection": {
  29. allocationProps1: nil,
  30. allocationProps2: nil,
  31. expected: nil,
  32. },
  33. "intersection, with labels/annotations, no aggregated metadata": {
  34. allocationProps1: &AllocationProperties{
  35. AggregatedMetadata: false,
  36. Node: "node1",
  37. Labels: map[string]string{"key1": "val1"},
  38. Annotations: map[string]string{"key2": "val2"},
  39. },
  40. allocationProps2: &AllocationProperties{
  41. AggregatedMetadata: false,
  42. Node: "node1",
  43. Labels: map[string]string{"key3": "val3"},
  44. Annotations: map[string]string{"key4": "val4"},
  45. },
  46. expected: &AllocationProperties{
  47. AggregatedMetadata: false,
  48. Node: "node1",
  49. Labels: nil,
  50. Annotations: nil,
  51. NamespaceLabels: map[string]string{},
  52. NamespaceAnnotations: map[string]string{},
  53. },
  54. },
  55. "intersection, with labels/annotations, same values": {
  56. allocationProps1: &AllocationProperties{
  57. AggregatedMetadata: false,
  58. ControllerKind: "controller1",
  59. Namespace: "ns1",
  60. Labels: map[string]string{"key1": "val1"},
  61. Annotations: map[string]string{"key2": "val2"},
  62. },
  63. allocationProps2: &AllocationProperties{
  64. AggregatedMetadata: true,
  65. ControllerKind: "controller2",
  66. Namespace: "ns1",
  67. Labels: map[string]string{"key1": "val1"},
  68. Annotations: map[string]string{"key2": "val2"},
  69. },
  70. expected: &AllocationProperties{
  71. AggregatedMetadata: true,
  72. Namespace: "ns1",
  73. ControllerKind: "",
  74. Labels: map[string]string{"key1": "val1"},
  75. Annotations: map[string]string{"key2": "val2"},
  76. NamespaceLabels: map[string]string{},
  77. NamespaceAnnotations: map[string]string{},
  78. },
  79. },
  80. "intersection, with labels/annotations, special case container": {
  81. allocationProps1: &AllocationProperties{
  82. AggregatedMetadata: false,
  83. Container: UnmountedSuffix,
  84. Namespace: "ns1",
  85. Labels: map[string]string{},
  86. Annotations: map[string]string{},
  87. },
  88. allocationProps2: &AllocationProperties{
  89. AggregatedMetadata: true,
  90. Container: "container3",
  91. Namespace: "ns1",
  92. Labels: map[string]string{"key1": "val1"},
  93. Annotations: map[string]string{"key2": "val2"},
  94. },
  95. expected: &AllocationProperties{
  96. AggregatedMetadata: true,
  97. Namespace: "ns1",
  98. ControllerKind: "",
  99. Labels: map[string]string{"key1": "val1"},
  100. Annotations: map[string]string{"key2": "val2"},
  101. NamespaceLabels: map[string]string{},
  102. NamespaceAnnotations: map[string]string{},
  103. },
  104. },
  105. "test services are nulled when intersecting": {
  106. allocationProps1: &AllocationProperties{
  107. AggregatedMetadata: false,
  108. Container: UnmountedSuffix,
  109. Namespace: "ns1",
  110. Services: []string{
  111. "cool",
  112. },
  113. Labels: map[string]string{},
  114. Annotations: map[string]string{},
  115. },
  116. allocationProps2: &AllocationProperties{
  117. AggregatedMetadata: true,
  118. Container: "container3",
  119. Namespace: "ns1",
  120. Labels: map[string]string{"key1": "val1"},
  121. Annotations: map[string]string{"key2": "val2"},
  122. },
  123. expected: &AllocationProperties{
  124. AggregatedMetadata: true,
  125. Namespace: "ns1",
  126. ControllerKind: "",
  127. Labels: map[string]string{"key1": "val1"},
  128. Annotations: map[string]string{"key2": "val2"},
  129. NamespaceLabels: map[string]string{},
  130. NamespaceAnnotations: map[string]string{},
  131. },
  132. },
  133. }
  134. for name, tc := range cases {
  135. t.Run(name, func(t *testing.T) {
  136. actual := tc.allocationProps1.Intersection(tc.allocationProps2)
  137. if !reflect.DeepEqual(actual, tc.expected) {
  138. t.Fatalf("test case %s: expected %+v; got %+v", name, tc.expected, actual)
  139. }
  140. })
  141. }
  142. }
  143. func TestGenerateKey(t *testing.T) {
  144. customOwnerLabelConfig := NewLabelConfig()
  145. customOwnerLabelConfig.OwnerLabel = "example_com_project"
  146. cases := map[string]struct {
  147. aggregate []string
  148. allocationProps *AllocationProperties
  149. labelConfig *LabelConfig
  150. expected string
  151. }{
  152. "aggregate by owner without owner labels": {
  153. aggregate: []string{"owner"},
  154. allocationProps: &AllocationProperties{
  155. Labels: map[string]string{"app": "cost-analyzer"},
  156. Annotations: map[string]string{"owner": "test owner 123"},
  157. },
  158. expected: "test owner 123",
  159. },
  160. "aggregate by owner without labels": {
  161. aggregate: []string{"owner"},
  162. allocationProps: &AllocationProperties{
  163. Annotations: map[string]string{"owner": "test owner 123"},
  164. },
  165. expected: "test owner 123",
  166. },
  167. "aggregate by owner with owner label and annotation": {
  168. aggregate: []string{"owner"},
  169. allocationProps: &AllocationProperties{
  170. Labels: map[string]string{"owner": "owner-label"},
  171. Annotations: map[string]string{"owner": "owner-annotation"},
  172. },
  173. expected: "owner-label",
  174. },
  175. "aggregate by environment with environment label and annotation": {
  176. aggregate: []string{"environment"},
  177. allocationProps: &AllocationProperties{
  178. Labels: map[string]string{"env": "environment-label"},
  179. Annotations: map[string]string{"env": "environment-annotation"},
  180. },
  181. expected: "environment-label",
  182. },
  183. "aggregate by department with department label and annotation": {
  184. aggregate: []string{"department"},
  185. allocationProps: &AllocationProperties{
  186. Labels: map[string]string{"department": "department-label"},
  187. Annotations: map[string]string{"department": "department-annotation"},
  188. },
  189. expected: "department-label",
  190. },
  191. "aggregate by team with team label and annotation": {
  192. aggregate: []string{"team"},
  193. allocationProps: &AllocationProperties{
  194. Labels: map[string]string{"team": "team-label"},
  195. Annotations: map[string]string{"team": "team-annotation"},
  196. },
  197. expected: "team-label",
  198. },
  199. "aggregate by product with product label and annotation": {
  200. aggregate: []string{"product"},
  201. allocationProps: &AllocationProperties{
  202. Labels: map[string]string{"app": "product-label"},
  203. Annotations: map[string]string{"app": "product-annotation"},
  204. },
  205. expected: "product-label",
  206. },
  207. "aggregate by product and owner with multiple labels and annotations": {
  208. aggregate: []string{"product", "owner"},
  209. allocationProps: &AllocationProperties{
  210. Labels: map[string]string{"app": "product-label", "owner": "owner-label", "team": "team-label"},
  211. Annotations: map[string]string{"app": "product-annotation", "owner": "owner-annotation", "team": "team-annotation"},
  212. },
  213. expected: "product-label/owner-label",
  214. },
  215. "user test": {
  216. aggregate: []string{"owner"},
  217. allocationProps: &AllocationProperties{
  218. Labels: map[string]string{"app_kubernetes_io_name": "x-mongo", "example_com_service_owner": "x", "component": "primary", "controller_revision_hash": "x-mongo-primary-x", "kubernetes_io_metadata_name": "app-microservices", "name": "app-microservices", "statefulset_kubernetes_io_pod_name": "x-mongo-primary-0"},
  219. Annotations: map[string]string{"example_com_project": "redacted"},
  220. },
  221. labelConfig: customOwnerLabelConfig,
  222. expected: "redacted",
  223. },
  224. }
  225. for name, tc := range cases {
  226. t.Run(name, func(t *testing.T) {
  227. lc := NewLabelConfig()
  228. if tc.labelConfig != nil {
  229. lc = tc.labelConfig
  230. }
  231. result := tc.allocationProps.GenerateKey(tc.aggregate, lc)
  232. if !reflect.DeepEqual(result, tc.expected) {
  233. t.Fatalf("expected %+v; got %+v", tc.expected, result)
  234. }
  235. })
  236. }
  237. }
  238. func TestIntersection(t *testing.T) {
  239. propsEmpty := AllocationProperties{}
  240. propsMedium := AllocationProperties{
  241. Cluster: "cluster1",
  242. Node: "Node1",
  243. Container: "container1",
  244. Controller: "controller1",
  245. ControllerKind: "controllerkind1",
  246. Namespace: "ns1",
  247. Pod: "pod1",
  248. Services: []string{"service1"},
  249. ProviderID: "provider1",
  250. }
  251. propsFull := AllocationProperties{
  252. Cluster: "cluster2",
  253. Node: "Node2",
  254. Container: "container2",
  255. Controller: "controller2",
  256. ControllerKind: "controllerkind2",
  257. Namespace: "ns2",
  258. Pod: "pod2",
  259. Services: []string{"service2"},
  260. ProviderID: "provider2",
  261. NamespaceLabels: AllocationLabels{"key1": "value1"},
  262. NamespaceAnnotations: AllocationAnnotations{"key2": "value2", "key5": "value5"},
  263. Labels: AllocationLabels{"key3": "value3"},
  264. Annotations: AllocationAnnotations{"key4": "value4"},
  265. }
  266. // Case 1: no intersection
  267. // expect empty result object
  268. testObj1 := AllocationProperties{}
  269. result := testObj1.Intersection(&propsEmpty)
  270. if !result.Equal(&propsEmpty) {
  271. t.Fatalf("Case 1: expected empty object, no intersection")
  272. }
  273. // Case 2: Only has labels/annotations
  274. // expect empty result object
  275. testObj2 := AllocationProperties{
  276. Labels: map[string]string{"app": "product-label-light"},
  277. Annotations: map[string]string{"app": "product-annotation-light"},
  278. }
  279. result = testObj2.Intersection(&propsMedium)
  280. if !result.Equal(&propsEmpty) {
  281. t.Fatalf("Case 2: expected empty object, no intersection")
  282. }
  283. // Case 3: Has non-label/annotations set
  284. // expect all non label/annotation/service string array fields to be unset
  285. // different container names should be omitted
  286. testObj3 := AllocationProperties{
  287. Cluster: "cluster1",
  288. Node: "Node1",
  289. Container: "container2",
  290. Controller: "controller1",
  291. ControllerKind: "controllerkind1",
  292. Namespace: "ns1",
  293. Pod: "pod1",
  294. Services: []string{"service1"},
  295. ProviderID: "provider1",
  296. }
  297. expectedResult := AllocationProperties{
  298. Cluster: "cluster1",
  299. Node: "Node1",
  300. Controller: "controller1",
  301. ControllerKind: "controllerkind1",
  302. Namespace: "ns1",
  303. Pod: "pod1",
  304. ProviderID: "provider1",
  305. }
  306. result = testObj3.Intersection(&propsMedium)
  307. if !result.Equal(&expectedResult) {
  308. t.Fatalf("Case 3: expected output %v does not match actual output %v", expectedResult, result)
  309. }
  310. // Case 4: Copy over NamespaceLabels/Annots when namespace is the same
  311. testObj4 := AllocationProperties{
  312. Cluster: "cluster2",
  313. Node: "NodeX",
  314. Container: "containerX",
  315. Controller: "controllerX",
  316. ControllerKind: "controllerkindX",
  317. Namespace: "ns2",
  318. Pod: "podX",
  319. Services: []string{"serviceX"},
  320. ProviderID: "providerX",
  321. NamespaceLabels: AllocationLabels{"key1": "value1"},
  322. NamespaceAnnotations: AllocationAnnotations{"key2": "value2", "key5": "value5"},
  323. Labels: AllocationLabels{"key3": "value3"},
  324. Annotations: AllocationAnnotations{"key4": "value4"},
  325. }
  326. expectedResult = AllocationProperties{
  327. Cluster: "cluster2",
  328. Namespace: "ns2",
  329. NamespaceLabels: AllocationLabels{"key1": "value1"},
  330. NamespaceAnnotations: AllocationAnnotations{"key2": "value2", "key5": "value5"},
  331. }
  332. result = testObj4.Intersection(&propsFull)
  333. if !result.Equal(&expectedResult) {
  334. t.Fatalf("Case 4: expected output %v does not match actual output %v", expectedResult, result)
  335. }
  336. }