pod_annotations_test.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package allocation
  2. // Description
  3. // Check Pod Annotations from API Match results from Prometheus
  4. import (
  5. "testing"
  6. "time"
  7. "github.com/opencost/opencost-integration-tests/pkg/api"
  8. "github.com/opencost/opencost-integration-tests/pkg/prometheus"
  9. )
  10. const podStatusResolution = "1m"
  11. func TestPodAnnotations(t *testing.T) {
  12. apiObj := api.NewAPI()
  13. testCases := []struct {
  14. name string
  15. window string
  16. aggregate string
  17. accumulate string
  18. includeAggregatedMetadata string
  19. }{
  20. {
  21. name: "Today",
  22. window: "24h",
  23. aggregate: "pod",
  24. accumulate: "true",
  25. includeAggregatedMetadata: "true",
  26. },
  27. {
  28. name: "Last Two Days",
  29. window: "48h",
  30. aggregate: "pod",
  31. accumulate: "true",
  32. includeAggregatedMetadata: "true",
  33. },
  34. }
  35. t.Logf("testCases: %v", testCases)
  36. for _, tc := range testCases {
  37. t.Run(tc.name, func(t *testing.T) {
  38. queryEnd := time.Now().UTC().Truncate(time.Hour).Add(time.Hour)
  39. endTime := queryEnd.Unix()
  40. // -------------------------------
  41. // Pod Annotations
  42. // avg_over_time(kube_pod_annotations{%s}[%s])
  43. // -------------------------------
  44. client := prometheus.NewClient()
  45. promAnnotationInfoInput := prometheus.PrometheusInput{}
  46. promAnnotationInfoInput.Metric = "kube_pod_annotations"
  47. promAnnotationInfoInput.Function = []string{"avg_over_time"}
  48. promAnnotationInfoInput.QueryWindow = tc.window
  49. promAnnotationInfoInput.Time = &endTime
  50. promAnnotationInfo, err := client.RunPromQLQuery(promAnnotationInfoInput, t)
  51. if err != nil {
  52. t.Fatalf("Error while calling Prometheus API %v", err)
  53. }
  54. // Pod Info
  55. promPodInfoInput := prometheus.PrometheusInput{}
  56. promPodInfoInput.Metric = "kube_pod_container_status_running"
  57. promPodInfoInput.MetricNotEqualTo = "0"
  58. promPodInfoInput.AggregateBy = []string{"container", "pod", "namespace", "node"}
  59. promPodInfoInput.Function = []string{"avg"}
  60. promPodInfoInput.AggregateWindow = tc.window
  61. promPodInfoInput.AggregateResolution = podStatusResolution
  62. promPodInfoInput.Time = &endTime
  63. podInfo, err := client.RunPromQLQuery(promPodInfoInput, t)
  64. if err != nil {
  65. t.Fatalf("Error while calling Prometheus API %v", err)
  66. }
  67. // Store Results in a Pod Map
  68. type PodData struct {
  69. Pod string
  70. Alive bool
  71. InAlloc bool
  72. promAnnotations map[string]string
  73. AllocAnnotations map[string]string
  74. }
  75. podMap := make(map[string]*PodData)
  76. // Store Prometheus Pod Prometheus Results
  77. for _, promAnnotation := range promAnnotationInfo.Data.Result {
  78. pod := promAnnotation.Metric.Pod
  79. Annotations := promAnnotation.Metric.Annotations
  80. podMap[pod] = &PodData{
  81. Pod: pod,
  82. promAnnotations: Annotations,
  83. }
  84. }
  85. for _, podInfoResponseItem := range podInfo.Data.Result {
  86. podMapItem, ok := podMap[podInfoResponseItem.Metric.Pod]
  87. if ok {
  88. podMapItem.Alive = true
  89. }
  90. }
  91. // API Response
  92. apiResponse, err := apiObj.GetAllocation(api.AllocationRequest{
  93. Window: tc.window,
  94. Aggregate: tc.aggregate,
  95. Accumulate: tc.accumulate,
  96. IncludeAggregatedMetadata: tc.includeAggregatedMetadata,
  97. })
  98. if err != nil {
  99. t.Fatalf("Error while calling Allocation API %v", err)
  100. }
  101. if apiResponse.Code != 200 {
  102. t.Errorf("API returned non-200 code")
  103. }
  104. // Store Allocation Pod Annotation Results
  105. for pod, allocationResponseItem := range apiResponse.Data[0] {
  106. podAnnotations, ok := podMap[pod]
  107. // No Annotations for this pod.
  108. // Not all pods have annotations
  109. if !ok {
  110. t.Logf("[Skipped] - No Annotations for Pod: %s", pod)
  111. continue
  112. }
  113. podAnnotations.InAlloc = true
  114. podAnnotations.AllocAnnotations = allocationResponseItem.Properties.Annotations
  115. }
  116. seenAnnotations := false
  117. // Compare Results
  118. for pod, podAnnotations := range podMap {
  119. t.Logf("Pod: %s", pod)
  120. if podAnnotations.Alive == false {
  121. t.Logf("Skipping %s. Pod Dead", pod)
  122. continue
  123. }
  124. // Skip pods that the Allocation API did not return. A
  125. // pod can appear in kube_pod_annotations and briefly in
  126. // kube_pod_container_status_running yet be absent from
  127. // /allocation, which only reports pods with coincident
  128. // usage metrics. Comparing annotations in that case is
  129. // a window-boundary race, not an annotation-propagation
  130. // bug.
  131. if !podAnnotations.InAlloc {
  132. t.Logf("Skipping %s. Pod not present in /allocation response.", pod)
  133. continue
  134. }
  135. // Prometheus Result will have fewer Annotations.
  136. // Allocation has oracle and feature related Annotations
  137. for promAnnotation, promAnnotationValue := range podAnnotations.promAnnotations {
  138. allocAnnotationValue, ok := podAnnotations.AllocAnnotations[promAnnotation]
  139. if !ok {
  140. t.Errorf(" - [Fail]: Prometheus Annotation %s not found in Allocation", promAnnotation)
  141. continue
  142. }
  143. seenAnnotations = true
  144. if allocAnnotationValue != promAnnotationValue {
  145. t.Errorf(" - [Fail]: Alloc %s != Prom %s", allocAnnotationValue, promAnnotationValue)
  146. } else {
  147. t.Logf(" - [Pass]: Annotation: %s", promAnnotation)
  148. }
  149. }
  150. }
  151. if !seenAnnotations {
  152. t.Fatalf("No Pod Annotations")
  153. }
  154. })
  155. }
  156. }