cloudcostprops.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package kubecost
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/opencost/opencost/pkg/log"
  6. )
  7. const (
  8. CloudCostInvoiceEntityIDProp string = "invoiceEntityID"
  9. CloudCostAccountIDProp string = "accountID"
  10. CloudCostProviderProp string = "provider"
  11. CloudCostProviderIDProp string = "providerID"
  12. CloudCostCategoryProp string = "category"
  13. CloudCostServiceProp string = "service"
  14. CloudCostLabelProp string = "label"
  15. )
  16. const (
  17. // CloudCostClusterManagementCategory describes CloudCost representing Hosted Kubernetes Fees
  18. CloudCostClusterManagementCategory string = "Cluster Management"
  19. // CloudCostDiskCategory describes CloudCost representing Disk usage
  20. CloudCostDiskCategory string = "Disk"
  21. // CloudCostLoadBalancerCategory describes CloudCost representing Load Balancer usage
  22. CloudCostLoadBalancerCategory string = "Load Balancer"
  23. // CloudCostNetworkCategory describes CloudCost representing Network usage
  24. CloudCostNetworkCategory string = "Network"
  25. // CloudCostVirtualMachineCategory describes CloudCost representing VM usage
  26. CloudCostVirtualMachineCategory string = "Virtual Machine"
  27. // CloudCostOtherCategory describes CloudCost that do not belong to a defined category
  28. CloudCostOtherCategory string = "Other"
  29. )
  30. type CloudCostLabels map[string]string
  31. func (ccl CloudCostLabels) Clone() CloudCostLabels {
  32. result := make(map[string]string, len(ccl))
  33. for k, v := range ccl {
  34. result[k] = v
  35. }
  36. return result
  37. }
  38. func (ccl CloudCostLabels) Equal(that CloudCostLabels) bool {
  39. if len(ccl) != len(that) {
  40. return false
  41. }
  42. // Maps are of equal length, so if all keys are in both maps, we don't
  43. // have to check the keys of the other map.
  44. for k, val := range ccl {
  45. if thatVal, ok := that[k]; !ok || val != thatVal {
  46. return false
  47. }
  48. }
  49. return true
  50. }
  51. // Intersection returns the set of labels that have the same key and value in the receiver and arg
  52. func (ccl CloudCostLabels) Intersection(that CloudCostLabels) CloudCostLabels {
  53. intersection := make(map[string]string)
  54. if len(ccl) == 0 || len(that) == 0 {
  55. return intersection
  56. }
  57. // Pick the smaller of the two label sets
  58. smallerLabels := ccl
  59. largerLabels := that
  60. if len(ccl) > len(that) {
  61. smallerLabels = that
  62. largerLabels = ccl
  63. }
  64. // Loop through the smaller label set
  65. for k, sVal := range smallerLabels {
  66. if lVal, ok := largerLabels[k]; ok && sVal == lVal {
  67. intersection[k] = sVal
  68. }
  69. }
  70. return intersection
  71. }
  72. type CloudCostProperties struct {
  73. ProviderID string `json:"providerID,omitempty"`
  74. Provider string `json:"provider,omitempty"`
  75. AccountID string `json:"accountID,omitempty"`
  76. InvoiceEntityID string `json:"invoiceEntityID,omitempty"`
  77. Service string `json:"service,omitempty"`
  78. Category string `json:"category,omitempty"`
  79. Labels CloudCostLabels `json:"labels,omitempty"`
  80. }
  81. func (ccp *CloudCostProperties) Equal(that *CloudCostProperties) bool {
  82. return ccp.ProviderID == that.ProviderID &&
  83. ccp.Provider == that.Provider &&
  84. ccp.AccountID == that.AccountID &&
  85. ccp.InvoiceEntityID == that.InvoiceEntityID &&
  86. ccp.Service == that.Service &&
  87. ccp.Category == that.Category &&
  88. ccp.Labels.Equal(that.Labels)
  89. }
  90. func (ccp *CloudCostProperties) Clone() *CloudCostProperties {
  91. return &CloudCostProperties{
  92. ProviderID: ccp.ProviderID,
  93. Provider: ccp.Provider,
  94. AccountID: ccp.AccountID,
  95. InvoiceEntityID: ccp.InvoiceEntityID,
  96. Service: ccp.Service,
  97. Category: ccp.Category,
  98. Labels: ccp.Labels.Clone(),
  99. }
  100. }
  101. // Intersection ensure the values of two CloudCostAggregateProperties are maintain only if they are equal
  102. func (ccp *CloudCostProperties) Intersection(that *CloudCostProperties) *CloudCostProperties {
  103. if ccp == nil || that == nil {
  104. return nil
  105. }
  106. if ccp.Equal(that) {
  107. return ccp
  108. }
  109. intersectionCCP := &CloudCostProperties{}
  110. if ccp.Equal(intersectionCCP) || that.Equal(intersectionCCP) {
  111. return intersectionCCP
  112. }
  113. if ccp.Provider == that.Provider {
  114. intersectionCCP.Provider = ccp.Provider
  115. }
  116. if ccp.ProviderID == that.ProviderID {
  117. intersectionCCP.ProviderID = ccp.ProviderID
  118. }
  119. if ccp.AccountID == that.AccountID {
  120. intersectionCCP.AccountID = ccp.AccountID
  121. }
  122. if ccp.InvoiceEntityID == that.InvoiceEntityID {
  123. intersectionCCP.InvoiceEntityID = ccp.InvoiceEntityID
  124. }
  125. if ccp.Service == that.Service {
  126. intersectionCCP.Service = ccp.Service
  127. }
  128. if ccp.Category == that.Category {
  129. intersectionCCP.Category = ccp.Category
  130. }
  131. intersectionCCP.Labels = ccp.Labels.Intersection(that.Labels)
  132. return intersectionCCP
  133. }
  134. func (ccp *CloudCostProperties) GenerateKey(props []string) string {
  135. if len(props) == 0 {
  136. return fmt.Sprintf("%s/%s/%s/%s/%s/%s", ccp.Provider, ccp.InvoiceEntityID, ccp.AccountID, ccp.Category, ccp.Service, ccp.ProviderID)
  137. }
  138. values := make([]string, len(props))
  139. for i, prop := range props {
  140. propVal := UnallocatedSuffix
  141. switch true {
  142. case prop == CloudCostProviderProp:
  143. if ccp.Provider != "" {
  144. propVal = ccp.Provider
  145. }
  146. case prop == CloudCostProviderIDProp:
  147. if ccp.ProviderID != "" {
  148. propVal = ccp.ProviderID
  149. }
  150. case prop == CloudCostCategoryProp:
  151. if ccp.Category != "" {
  152. propVal = ccp.Category
  153. }
  154. case prop == CloudCostInvoiceEntityIDProp:
  155. if ccp.InvoiceEntityID != "" {
  156. propVal = ccp.InvoiceEntityID
  157. }
  158. case prop == CloudCostAccountIDProp:
  159. if ccp.AccountID != "" {
  160. propVal = ccp.AccountID
  161. }
  162. case prop == CloudCostServiceProp:
  163. if ccp.Service != "" {
  164. propVal = ccp.Service
  165. }
  166. case strings.HasPrefix(prop, "label:"):
  167. labels := ccp.Labels
  168. if labels != nil {
  169. labelName := strings.TrimPrefix(prop, "label:")
  170. if labelValue, ok := labels[labelName]; ok && labelValue != "" {
  171. propVal = labelValue
  172. }
  173. }
  174. default:
  175. // This case should never be reached, as input up until this point
  176. // should be checked and rejected if invalid. But if we do get a
  177. // value we don't recognize, log a warning.
  178. log.Warnf("CloudCost: GenerateKey: illegal aggregation parameter: %s", prop)
  179. }
  180. values[i] = propVal
  181. }
  182. return strings.Join(values, "/")
  183. }