csv_export_test.go 6.6 KB

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