historical_pod_test.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package costmodel_test
  2. import (
  3. "fmt"
  4. "log"
  5. "net"
  6. "net/http"
  7. "os"
  8. "path/filepath"
  9. "testing"
  10. "time"
  11. "k8s.io/klog"
  12. "gotest.tools/assert"
  13. "github.com/kubecost/cost-model/cloud"
  14. costModel "github.com/kubecost/cost-model/costmodel"
  15. v1 "k8s.io/api/core/v1"
  16. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  17. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  18. "k8s.io/apimachinery/pkg/runtime/schema"
  19. "k8s.io/client-go/dynamic"
  20. "k8s.io/client-go/kubernetes"
  21. "k8s.io/client-go/rest"
  22. "k8s.io/client-go/tools/clientcmd"
  23. prometheusClient "github.com/prometheus/client_golang/api"
  24. _ "k8s.io/client-go/plugin/pkg/client/auth"
  25. )
  26. var PrometheusEndpoint string
  27. const PROMETHEUS_SERVER_ENDPOINT = "PROMETHEUS_SERVER_ENDPOINT"
  28. func homeDir() string {
  29. if h := os.Getenv("HOME"); h != "" {
  30. return h
  31. }
  32. return os.Getenv("USERPROFILE") // windows
  33. }
  34. func getKubernetesClient() (*kubernetes.Clientset, error) {
  35. var kubeconfig string
  36. config, err := rest.InClusterConfig()
  37. if err != nil {
  38. if home := homeDir(); home != "" {
  39. kubeconfig = filepath.Join(home, ".kube", "config")
  40. } else {
  41. return nil, fmt.Errorf("Unable to find home directory")
  42. }
  43. config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
  44. if err != nil {
  45. return nil, err
  46. }
  47. }
  48. return kubernetes.NewForConfig(config)
  49. }
  50. func getDynamicKubernetesClient() (dynamic.Interface, error) {
  51. config, err := rest.InClusterConfig()
  52. if err != nil {
  53. var kubeconfig string
  54. if home := homeDir(); home != "" {
  55. kubeconfig = filepath.Join(home, ".kube", "config")
  56. } else {
  57. return nil, fmt.Errorf("Unable to find home directory")
  58. }
  59. config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
  60. if err != nil {
  61. return nil, err
  62. }
  63. }
  64. return dynamic.NewForConfig(config)
  65. }
  66. func TestPodUpDown(t *testing.T) {
  67. client, err := getDynamicKubernetesClient()
  68. if err != nil {
  69. panic(err)
  70. }
  71. rclient, err := getKubernetesClient()
  72. if err != nil {
  73. panic(err)
  74. }
  75. var LongTimeoutRoundTripper http.RoundTripper = &http.Transport{ // may be necessary for long prometheus queries. TODO: make this configurable
  76. Proxy: http.ProxyFromEnvironment,
  77. DialContext: (&net.Dialer{
  78. Timeout: 120 * time.Second,
  79. KeepAlive: 120 * time.Second,
  80. }).DialContext,
  81. TLSHandshakeTimeout: 10 * time.Second,
  82. }
  83. a := os.Getenv(PROMETHEUS_SERVER_ENDPOINT)
  84. pc := prometheusClient.Config{
  85. Address: a,
  86. RoundTripper: LongTimeoutRoundTripper,
  87. }
  88. promCli, err := prometheusClient.NewClient(pc)
  89. if err != nil {
  90. panic(err)
  91. }
  92. cm := costModel.NewCostModel(rclient)
  93. deployment := &unstructured.Unstructured{
  94. Object: map[string]interface{}{
  95. "apiVersion": "apps/v1",
  96. "kind": "Deployment",
  97. "metadata": map[string]interface{}{
  98. "name": "demo-deployment",
  99. },
  100. "spec": map[string]interface{}{
  101. "replicas": 2,
  102. "selector": map[string]interface{}{
  103. "matchLabels": map[string]interface{}{
  104. "app": "demo",
  105. },
  106. },
  107. "template": map[string]interface{}{
  108. "metadata": map[string]interface{}{
  109. "labels": map[string]interface{}{
  110. "app": "demo",
  111. },
  112. },
  113. "spec": map[string]interface{}{
  114. "containers": []map[string]interface{}{
  115. {
  116. "name": "web",
  117. "image": "nginx:1.12",
  118. "resources": map[string]interface{}{
  119. "requests": map[string]interface{}{
  120. "memory": "64Mi",
  121. "cpu": "250m",
  122. },
  123. },
  124. "ports": []map[string]interface{}{
  125. {
  126. "name": "http",
  127. "protocol": "TCP",
  128. "containerPort": 80,
  129. },
  130. },
  131. },
  132. },
  133. },
  134. },
  135. },
  136. },
  137. }
  138. deploymentRes := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
  139. labels := make(map[string]string)
  140. labels["testaggregation"] = "foo"
  141. namespace := &v1.Namespace{
  142. ObjectMeta: metav1.ObjectMeta{
  143. Name: "test2",
  144. Labels: labels,
  145. },
  146. }
  147. klog.Infof("Creating namespace test2")
  148. rclient.CoreV1().Namespaces().Create(namespace)
  149. klog.Infof("Creating deployments in test2")
  150. _, err = client.Resource(deploymentRes).Namespace("test2").Create(deployment, metav1.CreateOptions{})
  151. if err != nil {
  152. panic(err)
  153. }
  154. klog.Infof("Sleeping 5 minutes to wait for steady state.")
  155. time.Sleep(5 * time.Minute)
  156. qr := `label_replace(label_replace(container_cpu_allocation{container='web',namespace='test2'}, "container_name", "$1", "container","(.+)"), "pod_name", "$1", "pod","(.+)")`
  157. end := time.Now()
  158. start := end.Add(-1 * time.Duration(3*time.Minute))
  159. step := time.Duration(time.Minute)
  160. res, err := costModel.QueryRange(promCli, qr, start, end, step)
  161. if err != nil {
  162. panic(err)
  163. }
  164. vectors, err := costModel.GetContainerMetricVectors(res, false, 0, "cluster-one")
  165. if err != nil {
  166. panic(err)
  167. }
  168. klog.Infof("Found Vectors %+v", vectors)
  169. if !(len(vectors) > 0) {
  170. panic("Expected vectors to have data")
  171. }
  172. for _, values := range vectors {
  173. assert.Check(t, len(values) > 0)
  174. for _, vector := range values {
  175. if vector.Value != 0.25 && vector.Value != 0.125 { // It's halved for fractional minute normalization.
  176. panic(fmt.Sprintf("Expected %f to equal 0.25", vector.Value))
  177. }
  178. }
  179. }
  180. deletePolicy := metav1.DeletePropagationForeground
  181. deleteOptions := &metav1.DeleteOptions{
  182. PropagationPolicy: &deletePolicy,
  183. }
  184. klog.Infof("Deleting deployment in namespace test2")
  185. if err := client.Resource(deploymentRes).Namespace("test2").Delete("demo-deployment", deleteOptions); err != nil {
  186. panic(err)
  187. }
  188. klog.Infof("Sleeping 5 minutes to wait for steady state.")
  189. time.Sleep(5 * time.Minute)
  190. res, err = costModel.Query(promCli, qr)
  191. if err != nil {
  192. panic(err)
  193. }
  194. vectors, err = costModel.GetContainerMetricVector(res, false, 0, "cluster-one")
  195. if err != nil {
  196. panic(err)
  197. }
  198. if len(vectors) != 0 {
  199. panic("Pods are not gone from namespace test2 data")
  200. }
  201. klog.Infof("Validated that pods are gone from namespace test2 data")
  202. provider, err := cloud.NewProvider(rclient, os.Getenv("CLOUD_PROVIDER_API_KEY"))
  203. if err != nil {
  204. panic(err)
  205. }
  206. loc, _ := time.LoadLocation("UTC")
  207. endTime := time.Now().In(loc)
  208. d, _ := time.ParseDuration("10m")
  209. startTime := endTime.Add(-1 * d)
  210. layout := "2006-01-02T15:04:05.000Z"
  211. startStr := startTime.Format(layout)
  212. endStr := endTime.Format(layout)
  213. log.Printf("Starting at %s \n", startStr)
  214. log.Printf("Ending at %s \n", endStr)
  215. provider.DownloadPricingData()
  216. data, err := cm.ComputeCostDataRange(promCli, rclient, provider, startStr, endStr, "1m", "", "", false)
  217. if err != nil {
  218. panic(err)
  219. }
  220. agg := costModel.AggregateCostData(data, "namespace", []string{""}, provider, nil)
  221. _, ok := agg["test"]
  222. assert.Assert(t, ok)
  223. _, ok = agg["test2"]
  224. if !ok {
  225. panic("No test2 namespace!")
  226. }
  227. data2, err := cm.ComputeCostData(promCli, rclient, provider, "10m", "", "")
  228. if err != nil {
  229. panic(err)
  230. }
  231. agg2 := costModel.AggregateCostData(data2, "namespace", []string{""}, provider, nil)
  232. _, ok2 := agg2["test"]
  233. assert.Assert(t, ok2)
  234. _, ok2 = agg2["test2"]
  235. if !ok2 {
  236. panic("No test2 namespace!")
  237. }
  238. agg3 := costModel.AggregateCostData(data, "label", []string{"testaggregation"}, provider, nil)
  239. _, ok3 := agg3["foo"]
  240. if !ok3 {
  241. panic("No label foo aggregate!")
  242. }
  243. }