allocation_csv_test.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package costmodel
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. "github.com/opencost/opencost/pkg/kubecost"
  9. )
  10. //go:generate moq -out moq_cloud_storage_test.go . CloudStorage:CloudStorageMock
  11. //go:generate moq -out moq_allocation_model_test.go . AllocationModel:AllocationModelMock
  12. func Test_UpdateCSV(t *testing.T) {
  13. t.Run("previous data doesn't exist, upload new data", func(t *testing.T) {
  14. storage := &CloudStorageMock{
  15. ExistsFunc: func(path string) (bool, error) {
  16. return false, nil
  17. },
  18. WriteFunc: func(name string, data []byte) error {
  19. return nil
  20. },
  21. }
  22. model := &AllocationModelMock{
  23. DateRangeFunc: func() (time.Time, time.Time, error) {
  24. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), nil
  25. },
  26. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*kubecost.AllocationSet, error) {
  27. return &kubecost.AllocationSet{
  28. Allocations: map[string]*kubecost.Allocation{
  29. "test": {
  30. Start: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), // required for GPU metrics
  31. End: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC),
  32. Name: "test",
  33. CPUCoreUsageAverage: 0.1,
  34. CPUCoreRequestAverage: 0.2,
  35. CPUCost: 0.3,
  36. RAMBytesUsageAverage: 0.4,
  37. RAMBytesRequestAverage: 0.5,
  38. RAMCost: 0.6,
  39. GPUHours: 48,
  40. GPUCost: 0.8,
  41. NetworkCost: 0.9,
  42. PVs: map[kubecost.PVKey]*kubecost.PVAllocation{
  43. kubecost.PVKey{
  44. Cluster: "test-cluster",
  45. Name: "test-pv",
  46. }: {
  47. ByteHours: 48,
  48. Cost: 2.0,
  49. },
  50. }, // 2 PVBytes, 2 PVCost
  51. },
  52. },
  53. }, nil
  54. },
  55. }
  56. err := UpdateCSV(context.TODO(), storage, model, "/test.csv")
  57. require.NoError(t, err)
  58. // uploaded a single file with the data
  59. assert.Len(t, storage.WriteCalls(), 1)
  60. assert.Len(t, model.ComputeAllocationCalls(), 1)
  61. assert.Equal(t, time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].Start)
  62. assert.Equal(t, time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].End)
  63. assert.Equal(t, `Date,Name,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost,GPUs,GPUCost,NetworkCost,PVBytes,PVCost,TotalCost
  64. 2021-01-01,test,0.1,0.2,0.3,0.4,0.5,0.6,2,0.8,0.9,2,2,4.6000000000000005
  65. `, string(storage.WriteCalls()[0].Data))
  66. })
  67. t.Run("merge new data with previous data (with different CSV structure)", func(t *testing.T) {
  68. storage := &CloudStorageMock{
  69. ExistsFunc: func(name string) (bool, error) {
  70. return true, nil
  71. },
  72. ReadFunc: func(name string) ([]byte, error) {
  73. return []byte(`Date,Name,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost
  74. 2021-01-01,test,0.1,0.2,0.3,0.4,0.5,0.6
  75. `), nil
  76. },
  77. WriteFunc: func(name string, data []byte) error {
  78. return nil
  79. },
  80. }
  81. model := &AllocationModelMock{
  82. DateRangeFunc: func() (time.Time, time.Time, error) {
  83. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), nil
  84. },
  85. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*kubecost.AllocationSet, error) {
  86. return &kubecost.AllocationSet{
  87. Allocations: map[string]*kubecost.Allocation{
  88. "test": {
  89. Name: "test",
  90. CPUCost: 1,
  91. },
  92. },
  93. }, nil
  94. },
  95. }
  96. err := UpdateCSV(context.TODO(), storage, model, "/test.csv")
  97. require.NoError(t, err)
  98. // uploaded a single file with the data
  99. assert.Len(t, storage.WriteCalls(), 1)
  100. assert.Len(t, model.ComputeAllocationCalls(), 1)
  101. assert.Len(t, model.ComputeAllocationCalls(), 1)
  102. // 2021-01-01 is already in the export file, so we only compute for 2021-01-02
  103. assert.Equal(t, time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].Start)
  104. assert.Equal(t, time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].End)
  105. assert.Equal(t, `Date,Name,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost,GPUs,GPUCost,NetworkCost,PVBytes,PVCost,TotalCost
  106. 2021-01-01,test,0.1,0.2,0.3,0.4,0.5,0.6,,,,,,
  107. 2021-01-02,test,0,0,1,0,0,0,0,0,0,0,0,1
  108. `, string(storage.WriteCalls()[0].Data))
  109. })
  110. t.Run("data already present in export file, export should be skipped", func(t *testing.T) {
  111. storage := &CloudStorageMock{
  112. ExistsFunc: func(name string) (bool, error) {
  113. return true, nil
  114. },
  115. ReadFunc: func(name string) ([]byte, error) {
  116. return []byte(`Date,Name,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost
  117. 2021-01-01,test,0.1,0.2,0.3,0.4,0.5,0.6
  118. `), nil
  119. },
  120. }
  121. model := &AllocationModelMock{
  122. DateRangeFunc: func() (time.Time, time.Time, error) {
  123. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), nil
  124. },
  125. }
  126. err := UpdateCSV(context.TODO(), storage, model, "/test.csv")
  127. require.NoError(t, err)
  128. assert.Len(t, storage.WriteCalls(), 0)
  129. assert.Len(t, model.ComputeAllocationCalls(), 0)
  130. })
  131. t.Run("allocation data is empty", func(t *testing.T) {
  132. model := &AllocationModelMock{
  133. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*kubecost.AllocationSet, error) {
  134. return &kubecost.AllocationSet{
  135. Allocations: nil,
  136. }, nil
  137. },
  138. DateRangeFunc: func() (time.Time, time.Time, error) {
  139. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), nil
  140. },
  141. }
  142. storage := &CloudStorageMock{
  143. ExistsFunc: func(name string) (bool, error) {
  144. return false, nil
  145. },
  146. }
  147. err := UpdateCSV(context.TODO(), storage, model, "/test.csv")
  148. require.Equal(t, err, errNoData)
  149. })
  150. }