promutil_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. package promutil
  2. import (
  3. "fmt"
  4. "reflect"
  5. "testing"
  6. "github.com/opencost/opencost/core/pkg/util/json"
  7. "github.com/stretchr/testify/assert"
  8. )
  9. func checkSlice(s1, s2 []string) error {
  10. if len(s1) != len(s2) {
  11. return fmt.Errorf("len(s1) [%d] != len(s2) [%d]", len(s1), len(s2))
  12. }
  13. for i := 0; i < len(s1); i++ {
  14. if s1[i] != s2[i] {
  15. return fmt.Errorf("At Index: %d. Different Values %s (s1) != %s (s2)", i, s1[i], s2[i])
  16. }
  17. }
  18. return nil
  19. }
  20. func TestEmptyKubeLabelsToPromLabels(t *testing.T) {
  21. labels, values := KubeLabelsToLabels(nil)
  22. if len(labels) != 0 {
  23. t.Errorf("Labels length is non-zero\n")
  24. }
  25. if len(values) != 0 {
  26. t.Errorf("Values length is non-zero\n")
  27. }
  28. labels, values = KubeLabelsToLabels(map[string]string{})
  29. if len(labels) != 0 {
  30. t.Errorf("Labels length is non-zero\n")
  31. }
  32. if len(values) != 0 {
  33. t.Errorf("Values length is non-zero\n")
  34. }
  35. }
  36. func TestKubeLabelsToPromLabels(t *testing.T) {
  37. var expectedLabels []string = []string{
  38. "label_app",
  39. "label_chart",
  40. "label_control_plane",
  41. "label_gatekeeper_sh_operation",
  42. "label_heritage",
  43. "label_pod_template_hash",
  44. "label_release",
  45. }
  46. var expectedValues []string = []string{
  47. "gatekeeper",
  48. "gatekeeper",
  49. "audit-controller",
  50. "audit",
  51. "Helm",
  52. "5599859cd4",
  53. "gatekeeper",
  54. }
  55. kubeLabels := map[string]string{
  56. "app": "gatekeeper",
  57. "chart": "gatekeeper",
  58. "control-plane": "audit-controller",
  59. "gatekeeper.sh/operation": "audit",
  60. "heritage": "Helm",
  61. "pod-template-hash": "5599859cd4",
  62. "release": "gatekeeper",
  63. }
  64. labels, values := KubePrependQualifierToLabels(kubeLabels, "label_")
  65. l2, v2 := KubeLabelsToLabels(kubeLabels)
  66. // Check to make sure we get expected labels and values returned
  67. err := checkSlice(labels, expectedLabels)
  68. if err != nil {
  69. t.Errorf("%s", err)
  70. }
  71. err = checkSlice(values, expectedValues)
  72. if err != nil {
  73. t.Errorf("%s", err)
  74. }
  75. // Check to make sure the helper function returns what the prependqualifier func
  76. // returns
  77. err = checkSlice(l2, labels)
  78. if err != nil {
  79. t.Errorf("%s", err)
  80. }
  81. err = checkSlice(v2, values)
  82. if err != nil {
  83. t.Errorf("%s", err)
  84. }
  85. }
  86. func TestKubePrependQualifierToLabelsDuplicates(t *testing.T) {
  87. // 7 expected labels/values
  88. expectedLabels := []string{
  89. "label_app_",
  90. "label_chart",
  91. "label_control_plane",
  92. "label_gatekeeper_sh_operation",
  93. "label_heritage",
  94. "label_pod_template_hash",
  95. "label_release",
  96. }
  97. expectedValues := []string{
  98. "gatekeeper",
  99. "gatekeeper",
  100. "audit-controller",
  101. "audit",
  102. "Helm",
  103. "5599859cd4",
  104. "gatekeeper",
  105. }
  106. // 8 input labels/values, with one duplicate label
  107. kubeLabels := map[string]string{
  108. // app- will be sanitized to app_
  109. "app-": "gatekeeper",
  110. "app_": "gatekeeper",
  111. "chart": "gatekeeper",
  112. "control-plane": "audit-controller",
  113. "gatekeeper.sh/operation": "audit",
  114. "heritage": "Helm",
  115. "pod-template-hash": "5599859cd4",
  116. "release": "gatekeeper",
  117. }
  118. labels, values := KubePrependQualifierToLabels(kubeLabels, "label_")
  119. // Check to make sure we get expected labels and values returned
  120. err := checkSlice(labels, expectedLabels)
  121. if err != nil {
  122. t.Errorf("%s", err)
  123. }
  124. err = checkSlice(values, expectedValues)
  125. if err != nil {
  126. t.Errorf("%s", err)
  127. }
  128. }
  129. func TestSanitizeLabels(t *testing.T) {
  130. type testCase struct {
  131. in map[string]string
  132. exp map[string]string
  133. }
  134. tcs := map[string]testCase{
  135. "empty labels": {
  136. in: map[string]string{},
  137. exp: map[string]string{},
  138. },
  139. "no op": {
  140. in: map[string]string{
  141. "foo": "bar",
  142. "baz": "loo",
  143. },
  144. exp: map[string]string{
  145. "foo": "bar",
  146. "baz": "loo",
  147. },
  148. },
  149. "modification, no collisions": {
  150. in: map[string]string{
  151. "foo-foo": "bar",
  152. "baz---baz": "loo",
  153. },
  154. exp: map[string]string{
  155. "foo_foo": "bar",
  156. "baz___baz": "loo",
  157. },
  158. },
  159. "modification, one collision": {
  160. in: map[string]string{
  161. "foo-foo": "bar",
  162. "foo+foo": "bar",
  163. "baz---baz": "loo",
  164. },
  165. exp: map[string]string{
  166. "foo_foo": "bar",
  167. "baz___baz": "loo",
  168. },
  169. },
  170. "modification, all collisions": {
  171. in: map[string]string{
  172. "foo-foo": "bar",
  173. "foo+foo": "bar",
  174. "foo_foo": "bar",
  175. },
  176. exp: map[string]string{
  177. "foo_foo": "bar",
  178. },
  179. },
  180. }
  181. for name, tc := range tcs {
  182. t.Run(name, func(t *testing.T) {
  183. act := SanitizeLabels(tc.in)
  184. if !reflect.DeepEqual(tc.exp, act) {
  185. t.Errorf("sanitizing labels failed for case %s: %+v != %+v", name, tc.exp, act)
  186. }
  187. })
  188. }
  189. }
  190. func TestClusterInfoLabels(t *testing.T) {
  191. expected := map[string]bool{"clusterprofile": true, "errorreporting": true, "id": true, "logcollection": true, "name": true, "productanalytics": true, "provider": true, "provisioner": true, "remotereadenabled": true, "thanosenabled": true, "valuesreporting": true, "version": true}
  192. clusterInfo := `{"clusterProfile":"production","errorReporting":"true","id":"cluster-one","logCollection":"true","name":"bolt-3","productAnalytics":"true","provider":"GCP","provisioner":"GKE","remoteReadEnabled":"false","thanosEnabled":"false","valuesReporting":"true","version":"1.14+"}`
  193. var m map[string]any
  194. err := json.Unmarshal([]byte(clusterInfo), &m)
  195. if err != nil {
  196. t.Errorf("Error: %s", err)
  197. return
  198. }
  199. labels := MapToLabels(m)
  200. for k := range expected {
  201. if _, ok := labels[k]; !ok {
  202. t.Errorf("Failed to locate key: \"%s\" in labels.", k)
  203. return
  204. }
  205. }
  206. }
  207. func TestPrependQualifierAndMerge(t *testing.T) {
  208. m := map[string]string{
  209. "a-a": "A",
  210. "b-b.c.d": "B",
  211. "cfg-c": "C",
  212. }
  213. exLabels := map[string]string{
  214. "node-type": "m1.large",
  215. "cluster.id": "cluster-a",
  216. "some-value": "524.2",
  217. }
  218. expected := map[string]string{
  219. "label_a_a": "A",
  220. "label_b_b_c_d": "B",
  221. "label_cfg_c": "C",
  222. "label_cluster_id": "cluster-a",
  223. "label_node_type": "m1.large",
  224. "label_some_value": "524.2",
  225. }
  226. result := KubePrependQualifierToLabelsAndMerge(m, exLabels, "label_")
  227. for k, v := range expected {
  228. val, ok := result[k]
  229. if !ok {
  230. t.Fatalf("Expected key: %s in result map, but was not found.", k)
  231. }
  232. if val != v {
  233. t.Fatalf("Expected value: %s for key: %s in result map. Got: %s", v, k, val)
  234. }
  235. }
  236. }
  237. func TestPrependQualifierToMap(t *testing.T) {
  238. m := map[string]string{
  239. "a-a": "A",
  240. "b-b.c.d": "B",
  241. "cfg-c": "C",
  242. }
  243. expected := map[string]string{
  244. "label_a_a": "A",
  245. "label_b_b_c_d": "B",
  246. "label_cfg_c": "C",
  247. }
  248. result := KubePrependQualifierToLabelsMap(m, "label_")
  249. for k, v := range expected {
  250. val, ok := result[k]
  251. if !ok {
  252. t.Fatalf("Expected key: %s in result map, but was not found.", k)
  253. }
  254. if val != v {
  255. t.Fatalf("Expected value: %s for key: %s in result map. Got: %s", v, k, val)
  256. }
  257. }
  258. }
  259. func TestPrependQualifierAndMerge_ExternalAddedToBase(t *testing.T) {
  260. base := map[string]string{"node": "worker-1"}
  261. external := map[string]string{"region": "us-east-1"}
  262. got := KubePrependQualifierToLabelsAndMerge(base, external, "label_")
  263. assert.Equal(t, map[string]string{"label_node": "worker-1", "label_region": "us-east-1"}, got)
  264. }
  265. func TestMerge_BaseWinsOnConflict(t *testing.T) {
  266. base := map[string]string{"region": "from-node"}
  267. external := map[string]string{"region": "from-configmap"}
  268. got := KubePrependQualifierToLabelsAndMerge(external, base, "label_")
  269. assert.Equal(t, "from-node", got["label_region"])
  270. }
  271. func TestMerge_EmptyExternal(t *testing.T) {
  272. base := map[string]string{"node": "worker-1"}
  273. baseWithLabelPrefix := map[string]string{"label_node": "worker-1"}
  274. got := KubePrependQualifierToLabelsAndMerge(map[string]string{}, base, "label_")
  275. assert.Equal(t, baseWithLabelPrefix, got)
  276. }
  277. func TestMerge_NilExternal(t *testing.T) {
  278. base := map[string]string{"node": "worker-1"}
  279. baseWithLabelPrefix := map[string]string{"label_node": "worker-1"}
  280. var external map[string]string
  281. got := KubePrependQualifierToLabelsAndMerge(external, base, "label_")
  282. assert.Equal(t, baseWithLabelPrefix, got)
  283. }
  284. func TestMerge_EmptyBase(t *testing.T) {
  285. external := map[string]string{"region": "us-east-1"}
  286. externalWithLabelPrefix := map[string]string{"label_region": "us-east-1"}
  287. got := KubePrependQualifierToLabelsAndMerge(external, map[string]string{}, "label_")
  288. assert.Equal(t, externalWithLabelPrefix, got)
  289. }
  290. func TestMerge_BothEmpty(t *testing.T) {
  291. got := KubePrependQualifierToLabelsAndMerge(map[string]string{}, map[string]string{}, "label_")
  292. assert.Empty(t, got)
  293. }
  294. // TestKubePrependQualifierToLabelsAndMerge proves the original base map is not modified.
  295. // A naive implementation using `out := base` copies the map header only,
  296. // so writes to out also mutate the caller's map.
  297. func TestMerge_DoesNotMutateBase(t *testing.T) {
  298. base := map[string]string{"node": "worker-1"}
  299. external := map[string]string{"region": "us-east-1"}
  300. _ = KubePrependQualifierToLabelsAndMerge(external, base, "label_")
  301. assert.Equal(t, map[string]string{"node": "worker-1"}, base, "Merge must not mutate the base map")
  302. }