provider.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package cloud
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "net/url"
  8. "os"
  9. "strings"
  10. "cloud.google.com/go/compute/metadata"
  11. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  12. "k8s.io/client-go/kubernetes"
  13. )
  14. // Node is the interface by which the provider and cost model communicate.
  15. // The provider will best-effort try to fill out this struct.
  16. type Node struct {
  17. Cost string `json:"hourlyCost"`
  18. VCPU string `json:"CPU"`
  19. VCPUCost string `json:"CPUHourlyCost"`
  20. RAM string `json:"RAM"`
  21. RAMCost string `json:"RAMGBHourlyCost"`
  22. Storage string `json:"storage"`
  23. StorageCost string `json:"storageHourlyCost"`
  24. UsesBaseCPUPrice bool `json:"usesDefaultPrice"`
  25. BaseCPUPrice string `json:"baseCPUPrice"` // Used to compute an implicit RAM GB/Hr price when RAM pricing is not provided.
  26. UsageType string `json:"usageType"`
  27. }
  28. // Provider represents a k8s provider.
  29. type Provider interface {
  30. ClusterName() ([]byte, error)
  31. AddServiceKey(url.Values) error
  32. GetDisks() ([]byte, error)
  33. NodePricing(string) (*Node, error)
  34. AllNodePricing() (interface{}, error)
  35. DownloadPricingData() error
  36. GetKey(map[string]string) string
  37. QuerySQL(string) ([]byte, error)
  38. }
  39. // GetDefaultPricingData will search for a json file representing pricing data in /models/ and use it for base pricing info.
  40. func GetDefaultPricingData(fname string) (*CustomPricing, error) {
  41. jsonFile, err := os.Open("/models/" + fname)
  42. if err != nil {
  43. return nil, err
  44. }
  45. defer jsonFile.Close()
  46. byteValue, err := ioutil.ReadAll(jsonFile)
  47. if err != nil {
  48. return nil, err
  49. }
  50. var customPricing = &CustomPricing{}
  51. err = json.Unmarshal([]byte(byteValue), customPricing)
  52. if err != nil {
  53. return nil, err
  54. }
  55. return customPricing, nil
  56. }
  57. type CustomPricing struct {
  58. Provider string `json:"provider"`
  59. Description string `json:"description"`
  60. CPU string `json:"CPU"`
  61. SpotCPU string `json:"spotCPU"`
  62. RAM string `json:"RAM"`
  63. SpotRAM string `json:"spotRAM"`
  64. SpotLabel string `json:"spotLabel,omitempty"`
  65. SpotLabelValue string `json:"spotLabelValue,omitempty"`
  66. }
  67. type NodePrice struct {
  68. CPU string
  69. RAM string
  70. }
  71. type CustomProvider struct {
  72. Clientset *kubernetes.Clientset
  73. Pricing map[string]*NodePrice
  74. SpotLabel string
  75. SpotLabelValue string
  76. }
  77. func (*CustomProvider) ClusterName() ([]byte, error) {
  78. return nil, nil
  79. }
  80. func (*CustomProvider) AddServiceKey(url.Values) error {
  81. return nil
  82. }
  83. func (*CustomProvider) GetDisks() ([]byte, error) {
  84. return nil, nil
  85. }
  86. func (c *CustomProvider) AllNodePricing() (interface{}, error) {
  87. return c.Pricing, nil
  88. }
  89. func (c *CustomProvider) NodePricing(key string) (*Node, error) {
  90. if _, ok := c.Pricing[key]; !ok {
  91. key = "default"
  92. }
  93. return &Node{
  94. VCPUCost: c.Pricing[key].CPU,
  95. RAMCost: c.Pricing[key].RAM,
  96. }, nil
  97. }
  98. func (c *CustomProvider) DownloadPricingData() error {
  99. if c.Pricing == nil {
  100. m := make(map[string]*NodePrice)
  101. c.Pricing = m
  102. }
  103. p, err := GetDefaultPricingData("default.json")
  104. if err != nil {
  105. return err
  106. }
  107. c.Pricing["default"] = &NodePrice{
  108. CPU: p.CPU,
  109. RAM: p.RAM,
  110. }
  111. c.Pricing["default,spot"] = &NodePrice{
  112. CPU: p.SpotCPU,
  113. RAM: p.SpotRAM,
  114. }
  115. return nil
  116. }
  117. func (c *CustomProvider) GetKey(labels map[string]string) string {
  118. if labels[c.SpotLabel] != "" && labels[c.SpotLabel] == c.SpotLabelValue {
  119. return "default,spot"
  120. }
  121. return "default" // TODO: multiple custom pricing support.
  122. }
  123. func (*CustomProvider) QuerySQL(query string) ([]byte, error) {
  124. return nil, nil
  125. }
  126. // NewProvider looks at the nodespec or provider metadata server to decide which provider to instantiate.
  127. func NewProvider(clientset *kubernetes.Clientset, apiKey string) (Provider, error) {
  128. if metadata.OnGCE() {
  129. if apiKey == "" {
  130. return nil, fmt.Errorf("Supply a GCP Key to start getting data")
  131. }
  132. return &GCP{
  133. Clientset: clientset,
  134. APIKey: apiKey,
  135. }, nil
  136. }
  137. nodes, err := clientset.CoreV1().Nodes().List(metav1.ListOptions{})
  138. if err != nil {
  139. return nil, err
  140. }
  141. provider := strings.ToLower(nodes.Items[0].Spec.ProviderID)
  142. if strings.HasPrefix(provider, "aws") {
  143. return &AWS{
  144. Clientset: clientset,
  145. }, nil
  146. } else if strings.HasPrefix(provider, "azure") {
  147. return &Azure{
  148. CustomProvider: &CustomProvider{
  149. Clientset: clientset,
  150. },
  151. }, nil
  152. } else {
  153. log.Printf("Unsupported provider, falling back to default")
  154. return &CustomProvider{
  155. Clientset: clientset,
  156. }, nil
  157. }
  158. }