usageapiintegration.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package oracle
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "time"
  7. "github.com/opencost/opencost/core/pkg/log"
  8. "github.com/opencost/opencost/core/pkg/opencost"
  9. "github.com/opencost/opencost/pkg/cloud"
  10. "github.com/oracle/oci-go-sdk/v65/common"
  11. "github.com/oracle/oci-go-sdk/v65/usageapi"
  12. )
  13. type UsageApiIntegration struct {
  14. UsageApiConfiguration
  15. ConnectionStatus cloud.ConnectionStatus
  16. }
  17. func (uai *UsageApiIntegration) GetCloudCost(start time.Time, end time.Time) (*opencost.CloudCostSetRange, error) {
  18. client, err := uai.GetUsageApiClient()
  19. if err != nil {
  20. uai.ConnectionStatus = cloud.FailedConnection
  21. return nil, fmt.Errorf("getting oracle usage api client: %s", err.Error())
  22. }
  23. req := usageapi.RequestSummarizedUsagesRequest{
  24. RequestSummarizedUsagesDetails: usageapi.RequestSummarizedUsagesDetails{
  25. Granularity: usageapi.RequestSummarizedUsagesDetailsGranularityDaily,
  26. GroupBy: []string{"resourceId", "service", "subscriptionId", "tenantName"},
  27. IsAggregateByTime: common.Bool(false),
  28. TimeUsageStarted: &common.SDKTime{Time: start},
  29. TimeUsageEnded: &common.SDKTime{Time: end},
  30. QueryType: usageapi.RequestSummarizedUsagesDetailsQueryTypeCost,
  31. TenantId: common.String(uai.TenancyID),
  32. },
  33. Limit: common.Int(500),
  34. }
  35. resp, err := client.RequestSummarizedUsages(context.Background(), req)
  36. if err != nil {
  37. uai.ConnectionStatus = cloud.FailedConnection
  38. return nil, fmt.Errorf("failed to query usage: %w", err)
  39. }
  40. ccsr, err := opencost.NewCloudCostSetRange(start, end, opencost.AccumulateOptionDay, uai.Key())
  41. if err != nil {
  42. return nil, err
  43. }
  44. // Set status to missing data if query comes back empty and the status isn't already successful
  45. if len(resp.Items) == 0 && uai.ConnectionStatus != cloud.SuccessfulConnection {
  46. uai.ConnectionStatus = cloud.MissingData
  47. return ccsr, nil
  48. }
  49. for _, item := range resp.Items {
  50. resourceId := ""
  51. if item.ResourceId != nil {
  52. resourceId = *item.ResourceId
  53. }
  54. tenantName := ""
  55. if item.TenantName != nil {
  56. tenantName = *item.TenantName
  57. }
  58. subscriptionId := ""
  59. if item.SubscriptionId != nil {
  60. subscriptionId = *item.SubscriptionId
  61. }
  62. service := ""
  63. if item.Service != nil {
  64. service = *item.Service
  65. }
  66. category := SelectOCICategory(service)
  67. // Iterate through the slice of tags, assigning
  68. // keys and values to the map of labels
  69. labels := opencost.CloudCostLabels{}
  70. for _, tag := range item.Tags {
  71. if tag.Key == nil || tag.Value == nil {
  72. continue
  73. }
  74. labels[*tag.Key] = *tag.Value
  75. }
  76. properties := &opencost.CloudCostProperties{
  77. ProviderID: resourceId,
  78. Provider: opencost.OracleProvider,
  79. AccountID: uai.TenancyID,
  80. AccountName: tenantName,
  81. InvoiceEntityID: subscriptionId,
  82. RegionID: uai.Region,
  83. Service: service,
  84. Category: category,
  85. Labels: labels,
  86. }
  87. winStart := item.TimeUsageStarted.Time
  88. winEnd := start.AddDate(0, 0, 1)
  89. listRate := 0.0
  90. if item.ListRate != nil {
  91. listRate = float64(*item.ListRate)
  92. }
  93. attrCostToParse := ""
  94. if item.AttributedCost != nil {
  95. attrCostToParse = *item.AttributedCost
  96. }
  97. attrCost, err := strconv.ParseFloat(attrCostToParse, 64)
  98. if err != nil {
  99. return nil, fmt.Errorf("unable to parse float '%s': %s", attrCostToParse, err.Error())
  100. }
  101. computedAmt := 0.0
  102. if item.ComputedAmount != nil {
  103. computedAmt = float64(*item.ComputedAmount)
  104. }
  105. cc := &opencost.CloudCost{
  106. Properties: properties,
  107. Window: opencost.NewWindow(&winStart, &winEnd),
  108. //todo: which returned costs go where?
  109. ListCost: opencost.CostMetric{
  110. Cost: listRate,
  111. },
  112. NetCost: opencost.CostMetric{
  113. Cost: computedAmt,
  114. },
  115. AmortizedNetCost: opencost.CostMetric{
  116. Cost: attrCost,
  117. },
  118. AmortizedCost: opencost.CostMetric{
  119. Cost: attrCost,
  120. },
  121. InvoicedCost: opencost.CostMetric{
  122. Cost: computedAmt,
  123. },
  124. }
  125. ccsr.LoadCloudCost(cc)
  126. }
  127. uai.ConnectionStatus = cloud.SuccessfulConnection
  128. return ccsr, nil
  129. }
  130. func (uai *UsageApiIntegration) GetStatus() cloud.ConnectionStatus {
  131. // initialize status if it has not done so; this can happen if the integration is inactive
  132. if uai.ConnectionStatus.String() == "" {
  133. uai.ConnectionStatus = cloud.InitialStatus
  134. }
  135. return uai.ConnectionStatus
  136. }
  137. func (uai *UsageApiIntegration) RefreshStatus() cloud.ConnectionStatus {
  138. log.Warn("status refresh is not supported for the Oracle provider")
  139. return uai.ConnectionStatus
  140. }
  141. func SelectOCICategory(service string) string {
  142. if service == "Compute" {
  143. return opencost.ComputeCategory
  144. } else if service == "Block Storage" || service == "Object Storage" {
  145. return opencost.StorageCategory
  146. } else if service == "Load Balancer" || service == "Virtual Cloud Network" {
  147. return opencost.NetworkCategory
  148. } else {
  149. return opencost.OtherCategory
  150. }
  151. }