allocation_csv_test.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. CPUCoreUsageAverage: 0.1,
  33. CPUCoreRequestAverage: 0.2,
  34. CPUCost: 0.3,
  35. RAMBytesUsageAverage: 0.4,
  36. RAMBytesRequestAverage: 0.5,
  37. RAMCost: 0.6,
  38. GPUHours: 48,
  39. GPUCost: 0.8,
  40. NetworkCost: 0.9,
  41. PVs: map[kubecost.PVKey]*kubecost.PVAllocation{
  42. kubecost.PVKey{
  43. Cluster: "test-cluster",
  44. Name: "test-pv",
  45. }: {
  46. ByteHours: 48,
  47. Cost: 2.0,
  48. },
  49. }, // 2 PVBytes, 2 PVCost
  50. Properties: &kubecost.AllocationProperties{
  51. Namespace: "test-namespace",
  52. Controller: "test-controller-name",
  53. ControllerKind: "test-controller-kind",
  54. Pod: "test-pod",
  55. Container: "test-container",
  56. },
  57. },
  58. },
  59. }, nil
  60. },
  61. }
  62. err := UpdateCSV(context.TODO(), storage, model, "/test.csv")
  63. require.NoError(t, err)
  64. // uploaded a single file with the data
  65. assert.Len(t, storage.WriteCalls(), 1)
  66. assert.Len(t, model.ComputeAllocationCalls(), 1)
  67. assert.Equal(t, time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].Start)
  68. assert.Equal(t, time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].End)
  69. assert.Equal(t, `Date,Namespace,ControllerKind,ControllerName,Pod,Container,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost,GPUs,GPUCost,NetworkCost,PVBytes,PVCost,TotalCost
  70. 2021-01-01,test-namespace,test-controller-kind,test-controller-name,test-pod,test-container,0.1,0.2,0.3,0.4,0.5,0.6,2,0.8,0.9,2,2,4.6000000000000005
  71. `, string(storage.WriteCalls()[0].Data))
  72. })
  73. t.Run("merge new data with previous data (with different CSV structure)", func(t *testing.T) {
  74. storage := &CloudStorageMock{
  75. ExistsFunc: func(name string) (bool, error) {
  76. return true, nil
  77. },
  78. ReadFunc: func(name string) ([]byte, error) {
  79. return []byte(`Date,Namespace,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost
  80. 2021-01-01,test-namespace,0.1,0.2,0.3,0.4,0.5,0.6
  81. `), nil
  82. },
  83. WriteFunc: func(name string, data []byte) error {
  84. return nil
  85. },
  86. }
  87. model := &AllocationModelMock{
  88. DateRangeFunc: func() (time.Time, time.Time, error) {
  89. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), nil
  90. },
  91. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*kubecost.AllocationSet, error) {
  92. return &kubecost.AllocationSet{
  93. Allocations: map[string]*kubecost.Allocation{
  94. "test": {
  95. Properties: &kubecost.AllocationProperties{
  96. Namespace: "test-namespace",
  97. },
  98. CPUCost: 1,
  99. },
  100. },
  101. }, nil
  102. },
  103. }
  104. err := UpdateCSV(context.TODO(), storage, model, "/test.csv")
  105. require.NoError(t, err)
  106. // uploaded a single file with the data
  107. assert.Len(t, storage.WriteCalls(), 1)
  108. assert.Len(t, model.ComputeAllocationCalls(), 1)
  109. assert.Len(t, model.ComputeAllocationCalls(), 1)
  110. // 2021-01-01 is already in the export file, so we only compute for 2021-01-02
  111. assert.Equal(t, time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].Start)
  112. assert.Equal(t, time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].End)
  113. assert.Equal(t, `Date,Namespace,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost,ControllerKind,ControllerName,Pod,Container,GPUs,GPUCost,NetworkCost,PVBytes,PVCost,TotalCost
  114. 2021-01-01,test-namespace,0.1,0.2,0.3,0.4,0.5,0.6,,,,,,,,,,
  115. 2021-01-02,test-namespace,0,0,1,0,0,0,,,,,0,0,0,0,0,1
  116. `, string(storage.WriteCalls()[0].Data))
  117. })
  118. t.Run("data already present in export file, export should be skipped", func(t *testing.T) {
  119. storage := &CloudStorageMock{
  120. ExistsFunc: func(name string) (bool, error) {
  121. return true, nil
  122. },
  123. ReadFunc: func(name string) ([]byte, error) {
  124. return []byte(`Date,Name,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost
  125. 2021-01-01,test,0.1,0.2,0.3,0.4,0.5,0.6
  126. `), nil
  127. },
  128. }
  129. model := &AllocationModelMock{
  130. DateRangeFunc: func() (time.Time, time.Time, error) {
  131. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), nil
  132. },
  133. }
  134. err := UpdateCSV(context.TODO(), storage, model, "/test.csv")
  135. require.NoError(t, err)
  136. assert.Len(t, storage.WriteCalls(), 0)
  137. assert.Len(t, model.ComputeAllocationCalls(), 0)
  138. })
  139. t.Run("allocation data is empty", func(t *testing.T) {
  140. model := &AllocationModelMock{
  141. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*kubecost.AllocationSet, error) {
  142. return &kubecost.AllocationSet{
  143. Allocations: nil,
  144. }, nil
  145. },
  146. DateRangeFunc: func() (time.Time, time.Time, error) {
  147. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), nil
  148. },
  149. }
  150. storage := &CloudStorageMock{
  151. ExistsFunc: func(name string) (bool, error) {
  152. return false, nil
  153. },
  154. }
  155. err := UpdateCSV(context.TODO(), storage, model, "/test.csv")
  156. require.Equal(t, err, errNoData)
  157. })
  158. }