2
0

cluster_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package costmodel_test
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. // "math"
  7. // "net"
  8. "net/http"
  9. "strconv"
  10. // "testing"
  11. // "time"
  12. // "gotest.tools/assert"
  13. prometheusClient "github.com/prometheus/client_golang/api"
  14. // v1 "k8s.io/api/core/v1"
  15. // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  16. _ "k8s.io/client-go/plugin/pkg/client/auth"
  17. "log"
  18. )
  19. const address = "http://localhost:9003"
  20. const apiPrefix = "/api/v1"
  21. const epQuery = apiPrefix + "/query"
  22. // The integration test assumes a GKE cluster in us-central-1 or an AWS cluster in us-east-2, with the following instance types
  23. // and storage classes.
  24. var prices = map[string]float64{
  25. "n1standardRAM": 0.004237,
  26. "n1standardCPU": 0.031611,
  27. "t2.medium": 0.0464,
  28. "t2.small": 0.023,
  29. "t2.micro": 0.0116,
  30. "c4.large": 0.1,
  31. "gp2": 0.000137,
  32. "ssd": 0.170,
  33. "Standard_DS2_v2": 0.252,
  34. "g1smallCPU": 0.025643,
  35. "g1smallRAM": 0.000034,
  36. "n1-highmem-2": 0.1171,
  37. }
  38. func parseQuery(qr interface{}) (float64, error) {
  39. data, ok := qr.(map[string]interface{})["data"]
  40. if !ok {
  41. return 0, fmt.Errorf("Improperly formatted response from prometheus, response %+v has no data field", data)
  42. }
  43. r, ok := data.(map[string]interface{})["result"]
  44. if !ok {
  45. return 0, fmt.Errorf("Improperly formatted data from prometheus, data has no result field")
  46. }
  47. results, ok := r.([]interface{})
  48. if !ok {
  49. return 0, fmt.Errorf("Improperly formatted results from prometheus, result field is not a slice")
  50. }
  51. val, ok := results[0].(map[string]interface{})["value"]
  52. if !ok {
  53. return 0, fmt.Errorf("Improperly formatted results from prometheus, value is not a field in the vector")
  54. }
  55. dataPoint, ok := val.([]interface{})
  56. if !ok || len(dataPoint) != 2 {
  57. return 0, fmt.Errorf("Improperly formatted datapoint from Prometheus")
  58. }
  59. return strconv.ParseFloat(dataPoint[1].(string), 64)
  60. }
  61. func query(cli prometheusClient.Client, query string) (interface{}, error) {
  62. u := cli.URL(epQuery, nil)
  63. q := u.Query()
  64. q.Set("query", query)
  65. u.RawQuery = q.Encode()
  66. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  67. if err != nil {
  68. return nil, err
  69. }
  70. _, body, _, err := cli.Do(context.Background(), req)
  71. if err != nil {
  72. return nil, err
  73. }
  74. var toReturn interface{}
  75. err = json.Unmarshal(body, &toReturn)
  76. if err != nil {
  77. log.Printf("ERROR" + err.Error())
  78. }
  79. return toReturn, err
  80. }
  81. /*
  82. func TestKubernetesPVCosts(t *testing.T) {
  83. cli, err := getKubernetesClient()
  84. if err != nil {
  85. panic(err)
  86. }
  87. var LongTimeoutRoundTripper http.RoundTripper = &http.Transport{ // may be necessary for long prometheus queries. TODO: make this configurable
  88. Proxy: http.ProxyFromEnvironment,
  89. DialContext: (&net.Dialer{
  90. Timeout: 120 * time.Second,
  91. KeepAlive: 120 * time.Second,
  92. }).DialContext,
  93. TLSHandshakeTimeout: 10 * time.Second,
  94. }
  95. pc := prometheusClient.Config{
  96. Address: address,
  97. RoundTripper: LongTimeoutRoundTripper,
  98. }
  99. promCli, err := prometheusClient.NewClient(pc)
  100. if err != nil {
  101. panic(err)
  102. }
  103. pvs, err := cli.CoreV1().PersistentVolumes().List(metav1.ListOptions{})
  104. if err != nil {
  105. panic(err)
  106. }
  107. for _, pv := range pvs.Items {
  108. name := pv.Name
  109. class := pv.Spec.StorageClassName
  110. q := fmt.Sprintf(`pv_hourly_cost{persistentvolume="%s"}`, name)
  111. qt, err := query(promCli, q)
  112. total, err := parseQuery(qt)
  113. if err != nil {
  114. log.Printf(err.Error())
  115. }
  116. if price, ok := prices[class]; ok {
  117. assert.Equal(t, math.Round(total*1000000)/1000000, price)
  118. }
  119. }
  120. }
  121. func TestKubernetesClusterCosts(t *testing.T) {
  122. prices["n1-standard-1"] = math.Round((prices["n1standardCPU"]+3.61219*prices["n1standardRAM"])*10000) / 10000
  123. prices["g1-small"] = math.Round(((prices["g1smallCPU"] + 0.216998*prices["g1smallRAM"]) * 10000)) / 10000
  124. cli, err := getKubernetesClient()
  125. if err != nil {
  126. panic(err)
  127. }
  128. var LongTimeoutRoundTripper http.RoundTripper = &http.Transport{ // may be necessary for long prometheus queries. TODO: make this configurable
  129. Proxy: http.ProxyFromEnvironment,
  130. DialContext: (&net.Dialer{
  131. Timeout: 120 * time.Second,
  132. KeepAlive: 120 * time.Second,
  133. }).DialContext,
  134. TLSHandshakeTimeout: 10 * time.Second,
  135. }
  136. pc := prometheusClient.Config{
  137. Address: address,
  138. RoundTripper: LongTimeoutRoundTripper,
  139. }
  140. promCli, err := prometheusClient.NewClient(pc)
  141. if err != nil {
  142. panic(err)
  143. }
  144. nodes, err := cli.CoreV1().Nodes().List(metav1.ListOptions{})
  145. if err != nil {
  146. panic(err)
  147. }
  148. for _, n := range nodes.Items {
  149. name := n.GetObjectMeta().GetName()
  150. q := fmt.Sprintf(`node_total_hourly_cost{instance="%s"}`, name)
  151. labels := n.GetObjectMeta().GetLabels()
  152. instanceType := labels[v1.LabelInstanceType]
  153. qt, err := query(promCli, q)
  154. if err != nil {
  155. panic(err)
  156. }
  157. total, err := parseQuery(qt)
  158. if err != nil {
  159. panic(err)
  160. }
  161. if price, ok := prices[instanceType]; ok {
  162. assert.Equal(t, math.Round(total*10000)/10000, price)
  163. }
  164. }
  165. }
  166. */