2
0

usageapiintegration.go 4.5 KB

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