boaquerier.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package alibaba
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/opencost/opencost/pkg/cloud"
  6. cloudconfig "github.com/opencost/opencost/pkg/cloud/config"
  7. "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
  8. "github.com/aliyun/alibaba-cloud-sdk-go/services/bssopenapi"
  9. "github.com/opencost/opencost/pkg/kubecost"
  10. "github.com/opencost/opencost/pkg/log"
  11. )
  12. const (
  13. boaIsNode = "i-" // isNode if prefix of instance_id is i-
  14. boaIsDisk = "d-" // isDisk if prefix is disk is d-
  15. boaIsNetwork = "piece" //usage unit of network resource in Alibaba is Piece
  16. )
  17. type BoaQuerier struct {
  18. BOAConfiguration
  19. ConnectionStatus cloud.ConnectionStatus
  20. }
  21. func (bq *BoaQuerier) GetStatus() cloud.ConnectionStatus {
  22. // initialize status if it has not done so; this can happen if the integration is inactive
  23. if bq.ConnectionStatus.String() == "" {
  24. bq.ConnectionStatus = cloud.InitialStatus
  25. }
  26. return bq.ConnectionStatus
  27. }
  28. func (bq *BoaQuerier) Equals(config cloudconfig.Config) bool {
  29. thatConfig, ok := config.(*BoaQuerier)
  30. if !ok {
  31. return false
  32. }
  33. return bq.BOAConfiguration.Equals(&thatConfig.BOAConfiguration)
  34. }
  35. // QueryInstanceBill performs the request to the BSS client and get the response for the current page number
  36. func (bq *BoaQuerier) QueryInstanceBill(client *bssopenapi.Client, isBillingItem bool, invocationScheme, granularity, billingCycle, billingDate string, pageNum int) (*bssopenapi.QueryInstanceBillResponse, error) {
  37. log.Debugf("QueryInstanceBill: query for BSS Open API for billing date: %s with pageNum: %d ", billingDate, pageNum)
  38. request := bssopenapi.CreateQueryInstanceBillRequest()
  39. request.Scheme = invocationScheme
  40. request.BillingCycle = billingCycle
  41. request.IsBillingItem = requests.NewBoolean(true)
  42. request.Granularity = granularity
  43. request.BillingDate = billingDate
  44. request.PageNum = requests.NewInteger(pageNum)
  45. response, err := client.QueryInstanceBill(request)
  46. if err != nil {
  47. return nil, fmt.Errorf("QueryInstanceBill: Failed to hit the BSS Open API with error for page num %d: %v", pageNum, err)
  48. }
  49. log.Debugf("QueryInstanceBill: Total Number of total items for billing Date: %s pageNum: %d is %d", billingDate, pageNum, response.Data.TotalCount)
  50. return response, nil
  51. }
  52. // QueryBoaPaginated Calls the API in a paginated fashion. There's no paramter in API that can distinguish if it hasMorePages
  53. // hence the logic of processedItem <= TotalItem.
  54. func (bq *BoaQuerier) QueryBoaPaginated(client *bssopenapi.Client, isBillingItem bool, invocationScheme, granularity, billingCycle, billingDate string, fn func(*bssopenapi.QueryInstanceBillResponse) bool) error {
  55. pageNum := 1
  56. processedItem := 0 // setting default here to hit the API for the first time
  57. totalItem := 1
  58. for processedItem < totalItem {
  59. log.Debugf("QueryBoaPaginated: query for BSS Open API for billing date: %s with pageNum: %d", billingDate, pageNum)
  60. response, err := bq.QueryInstanceBill(client, isBillingItem, invocationScheme, granularity, billingCycle, billingDate, pageNum)
  61. if err != nil {
  62. return fmt.Errorf("QueryBoaPaginated for billing cycle : %s, billing date: %s, page num %d: %v", billingCycle, billingDate, pageNum, err)
  63. }
  64. fn(response)
  65. totalItem = response.Data.TotalCount
  66. processedItem += response.Data.PageSize
  67. pageNum += 1
  68. }
  69. return nil
  70. }
  71. // GetBoaQueryInstanceBillFunc gives the item to the handler function in boaIntegration.go to process
  72. // computeItem, topNItem and aggregatedItem
  73. func GetBoaQueryInstanceBillFunc(fn func(bssopenapi.Item) error, billingDate string) func(output *bssopenapi.QueryInstanceBillResponse) bool {
  74. processBOAItems := func(output *bssopenapi.QueryInstanceBillResponse) bool {
  75. // This could be connection error were unable to fetch response output from Client
  76. if output == nil {
  77. log.Errorf("BoaQuerier: No Response from the ALibaba BSS Open API client for billing Date: %s", billingDate)
  78. return false
  79. }
  80. // These infer that the rest call was successful but the Cloud Usage resource for those days were 0
  81. if output.Data.TotalCount == 0 {
  82. log.Warnf("BoaQuerier: Total Item Count is 0 for billing Date: %s ", billingDate)
  83. return false
  84. }
  85. for _, item := range output.Data.Items.Item {
  86. fn(item)
  87. }
  88. return true
  89. }
  90. return processBOAItems
  91. }
  92. // SelectAlibabaCategory processes the Alibaba service to associated Kubecost category
  93. func SelectAlibabaCategory(item bssopenapi.Item) string {
  94. if (item != bssopenapi.Item{}) {
  95. // Provider ID has prefix "i-" for node in Alibaba
  96. if strings.HasPrefix(item.InstanceID, boaIsNode) {
  97. return kubecost.ComputeCategory
  98. }
  99. // Provider ID for disk start with "d-" for storage type in Alibaba
  100. if strings.HasPrefix(item.InstanceID, boaIsDisk) {
  101. return kubecost.StorageCategory
  102. }
  103. // Network has the highest priority and is based on the usage type of "piece" in Alibaba
  104. if item.UsageUnit == boaIsNetwork {
  105. return kubecost.NetworkCategory
  106. }
  107. }
  108. // Alibaba CUR integration report has service lower case mostly unlike AWS
  109. // TO-DO: Can investigate further product codes but bare minimal differentiation for start
  110. switch strings.ToLower(item.ProductCode) {
  111. case "slb", "eip", "nis", "gtm":
  112. return kubecost.NetworkCategory
  113. case "ecs", "eds", "sas":
  114. return kubecost.ComputeCategory
  115. case "ack":
  116. return kubecost.ManagementCategory
  117. case "ebs", "oss", "scu":
  118. return kubecost.StorageCategory
  119. default:
  120. return kubecost.OtherCategory
  121. }
  122. }