allocations_summary_running_pods_test.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package count
  2. // Description - Checks for the allocation summary of pods for each namespace is the same for a prometheus request
  3. // and allocation/summary API request
  4. import (
  5. // "fmt"
  6. "slices"
  7. "sort"
  8. "strings"
  9. "testing"
  10. "time"
  11. "github.com/opencost/opencost-integration-tests/pkg/api"
  12. "github.com/opencost/opencost-integration-tests/pkg/prometheus"
  13. "github.com/pmezard/go-difflib/difflib"
  14. )
  15. func TestQueryAllocationSummary(t *testing.T) {
  16. apiObj := api.NewAPI()
  17. testCases := []struct {
  18. name string
  19. window string
  20. aggregate string
  21. accumulate string
  22. }{
  23. {
  24. name: "Yesterday",
  25. window: "24h",
  26. aggregate: "pod",
  27. accumulate: "false",
  28. },
  29. }
  30. t.Logf("testCases: %v", testCases)
  31. for _, tc := range testCases {
  32. t.Run(tc.name, func(t *testing.T) {
  33. // API Client
  34. apiResponse, err := apiObj.GetAllocationSummary(api.AllocationRequest{
  35. Window: tc.window,
  36. Aggregate: tc.aggregate,
  37. Accumulate: tc.accumulate,
  38. })
  39. if err != nil {
  40. t.Fatalf("Error while calling Allocation API %v", err)
  41. }
  42. if apiResponse.Code != 200 {
  43. t.Errorf("API returned non-200 code")
  44. }
  45. queryEnd := time.Now().UTC().Truncate(time.Hour).Add(time.Hour)
  46. endTime := queryEnd.Unix()
  47. // Prometheus Client
  48. // Want to Run avg(avg_over_time(kube_pod_container_status_running[24h]) != 0) by (container, pod, namespace)
  49. // Running avg(avg_over_time(kube_pod_container_status_running[24h])) by (container, pod, namespace)
  50. client := prometheus.NewClient()
  51. promInput := prometheus.PrometheusInput{
  52. Metric: "kube_pod_container_status_running",
  53. // MetricNotEqualTo: "0",
  54. Function: []string{"avg_over_time", "avg"},
  55. QueryWindow: tc.window,
  56. AggregateBy: []string{"container", "pod", "namespace"},
  57. Time: &endTime,
  58. }
  59. promResponse, err := client.RunPromQLQuery(promInput, t)
  60. if err != nil {
  61. t.Fatalf("Error while calling Prometheus API %v", err)
  62. }
  63. // Narrow the Prometheus pod set to pods alive at the query
  64. // endTime using a 1m-resolution subquery. Without this,
  65. // pods that were only very briefly running inside the 24h
  66. // window show up in Prometheus (as their avg_over_time is
  67. // non-zero) but are absent from /allocation/summary, which
  68. // only reports pods with coincident usage samples. That is
  69. // a window-boundary race, not a pod-count bug.
  70. promAliveInput := prometheus.PrometheusInput{
  71. Metric: "kube_pod_container_status_running",
  72. MetricNotEqualTo: "0",
  73. Function: []string{"avg"},
  74. AggregateBy: []string{"container", "pod", "namespace", "node"},
  75. AggregateWindow: tc.window,
  76. AggregateResolution: "1m",
  77. Time: &endTime,
  78. }
  79. promAliveResponse, err := client.RunPromQLQuery(promAliveInput, t)
  80. if err != nil {
  81. t.Fatalf("Error while calling Prometheus API %v", err)
  82. }
  83. alivePods := make(map[string]bool)
  84. for _, metric := range promAliveResponse.Data.Result {
  85. alivePods[metric.Metric.Pod] = true
  86. }
  87. var apiAllocationPodNames []string
  88. for podName, _ := range apiResponse.Data.Sets[0].Allocations {
  89. // Synthetic value generated and returned by /allocation and not /prometheus
  90. if slices.Contains([]string{"prometheus-system-unmounted-pvcs", "network-load-gen-unmounted-pvcs"}, podName) {
  91. continue
  92. }
  93. if !slices.Contains(apiAllocationPodNames, podName) {
  94. apiAllocationPodNames = append(apiAllocationPodNames, podName)
  95. }
  96. }
  97. var promPodNames []string
  98. for _, promItem := range promResponse.Data.Result {
  99. // This pod was down, unable to do it with the query
  100. if promItem.Value.Value == 0 {
  101. continue
  102. }
  103. // Skip pods that are not alive at the query end time.
  104. // /allocation/summary only returns pods with usage data
  105. // in the window, so short-lived pods that were up
  106. // earlier in the 24h window but not at endTime would
  107. // otherwise produce spurious mismatches.
  108. if !alivePods[promItem.Metric.Pod] {
  109. continue
  110. }
  111. if !slices.Contains(promPodNames, promItem.Metric.Pod) {
  112. promPodNames = append(promPodNames, promItem.Metric.Pod)
  113. }
  114. }
  115. apiAllocationsSummaryCount := len(apiAllocationPodNames)
  116. promAllocationsSummaryCount := len(promPodNames)
  117. // sort the string slices
  118. sort.Strings(promPodNames)
  119. sort.Strings(apiAllocationPodNames)
  120. promPodNamesString := strings.Join(promPodNames, "\n")
  121. apiAllocationPodNamesString := strings.Join(apiAllocationPodNames, "\n")
  122. // Old version file are Prometheus Results and New Version filea are API Allocation Results
  123. if apiAllocationsSummaryCount != promAllocationsSummaryCount {
  124. diff := difflib.UnifiedDiff{
  125. A: difflib.SplitLines(promPodNamesString),
  126. B: difflib.SplitLines(apiAllocationPodNamesString),
  127. FromFile: "Original",
  128. ToFile: "Current",
  129. Context: 3,
  130. }
  131. podNamesDiff, _ := difflib.GetUnifiedDiffString(diff)
  132. t.Errorf("[Fail]: Number of Pods from Prometheus(%d) and /allocation/summary (%d) did not match.\n Unified Diff:\n %s", promAllocationsSummaryCount, apiAllocationsSummaryCount, podNamesDiff)
  133. } else {
  134. t.Logf("[Pass]: Number of Pods from Promtheus and /allocation/summary Match.")
  135. }
  136. })
  137. }
  138. }