2
0

csv_export_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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/core/pkg/opencost"
  9. "github.com/opencost/opencost/pkg/filemanager"
  10. )
  11. //go:generate moq -out moq_cloud_storage_test.go . CloudStorage:CloudStorageMock
  12. //go:generate moq -out moq_allocation_model_test.go . AllocationModel:AllocationModelMock
  13. func Test_UpdateCSV(t *testing.T) {
  14. t.Run("previous data doesn't exist, upload new data", func(t *testing.T) {
  15. storage := &filemanager.InMemoryFile{}
  16. model := &AllocationModelMock{
  17. DateRangeFunc: func(_ int) (time.Time, time.Time, error) {
  18. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), nil
  19. },
  20. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*opencost.AllocationSet, error) {
  21. return &opencost.AllocationSet{
  22. Allocations: map[string]*opencost.Allocation{
  23. "test": {
  24. Start: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), // required for GPU metrics
  25. End: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC),
  26. CPUCoreUsageAverage: 0.1,
  27. CPUCoreRequestAverage: 0.2,
  28. CPUCost: 0.3,
  29. RAMBytesUsageAverage: 0.4,
  30. RAMBytesRequestAverage: 0.5,
  31. RAMCost: 0.6,
  32. GPUHours: 48,
  33. GPUCost: 0.8,
  34. NetworkCost: 0.9,
  35. NetworkTransferBytes: 10,
  36. NetworkReceiveBytes: 11,
  37. PVs: map[opencost.PVKey]*opencost.PVAllocation{
  38. {
  39. Cluster: "test-cluster",
  40. Name: "test-pv",
  41. }: {
  42. ByteHours: 48,
  43. Cost: 2.0,
  44. },
  45. }, // 2 PVBytes, 2 PVCost
  46. Properties: &opencost.AllocationProperties{
  47. Namespace: "test-namespace",
  48. Controller: "test-controller-name",
  49. ControllerKind: "test-controller-kind",
  50. Pod: "test-pod",
  51. Container: "test-container",
  52. },
  53. },
  54. },
  55. }, nil
  56. },
  57. }
  58. err := UpdateCSV(context.TODO(), storage, model, false, nil)
  59. require.NoError(t, err)
  60. // uploaded a single file with the data
  61. assert.Len(t, model.ComputeAllocationCalls(), 1)
  62. assert.Equal(t, time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].Start)
  63. assert.Equal(t, time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].End)
  64. assert.Equal(t, `Date,Namespace,ControllerKind,ControllerName,Pod,Container,CPUCoreUsageAverage,CPUCoreRequestAverage,RAMBytesUsageAverage,RAMBytesRequestAverage,NetworkReceiveBytes,NetworkTransferBytes,GPUs,PVBytes,CPUCost,RAMCost,NetworkCost,PVCost,GPUCost,TotalCost
  65. 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
  66. `, string(storage.Data))
  67. })
  68. t.Run("export labels", func(t *testing.T) {
  69. storage := &filemanager.InMemoryFile{}
  70. model := &AllocationModelMock{
  71. DateRangeFunc: func(_ int) (time.Time, time.Time, error) {
  72. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), nil
  73. },
  74. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*opencost.AllocationSet, error) {
  75. return &opencost.AllocationSet{
  76. Allocations: map[string]*opencost.Allocation{
  77. "test": {
  78. Properties: &opencost.AllocationProperties{
  79. Namespace: "test-namespace",
  80. Controller: "test-controller-name",
  81. ControllerKind: "test-controller-kind",
  82. Pod: "test-pod",
  83. Container: "test-container",
  84. Labels: map[string]string{
  85. "test-label1": "test-value1",
  86. "test-label2": "test-value2",
  87. },
  88. },
  89. },
  90. },
  91. }, nil
  92. },
  93. }
  94. err := UpdateCSV(context.TODO(), storage, model, true, []string{"test-label1", "test-label2"})
  95. require.NoError(t, err)
  96. // uploaded a single file with the data
  97. assert.Len(t, model.ComputeAllocationCalls(), 1)
  98. assert.Equal(t, `Date,Namespace,ControllerKind,ControllerName,Pod,Container,CPUCoreUsageAverage,CPUCoreRequestAverage,RAMBytesUsageAverage,RAMBytesRequestAverage,NetworkReceiveBytes,NetworkTransferBytes,GPUs,PVBytes,CPUCost,RAMCost,NetworkCost,PVCost,GPUCost,TotalCost,Labels,Label_test-label1,Label_test-label2
  99. 2021-01-01,test-namespace,test-controller-kind,test-controller-name,test-pod,test-container,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"{""test-label1"":""test-value1"",""test-label2"":""test-value2""}",test-value1,test-value2
  100. `, string(storage.Data))
  101. })
  102. t.Run("merge new data with previous data (with different CSV structure)", func(t *testing.T) {
  103. storage := &filemanager.InMemoryFile{
  104. Data: []byte(`Date,Namespace,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost,Label_app
  105. 2021-01-01,test-namespace,0.1,0.2,0.3,0.4,0.5,0.6,app1
  106. `),
  107. }
  108. model := &AllocationModelMock{
  109. DateRangeFunc: func(_ int) (time.Time, time.Time, error) {
  110. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), nil
  111. },
  112. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*opencost.AllocationSet, error) {
  113. return &opencost.AllocationSet{
  114. Allocations: map[string]*opencost.Allocation{
  115. "test": {
  116. Properties: &opencost.AllocationProperties{
  117. Namespace: "test-namespace",
  118. },
  119. CPUCost: 1,
  120. },
  121. },
  122. }, nil
  123. },
  124. }
  125. err := UpdateCSV(context.TODO(), storage, model, false, nil)
  126. require.NoError(t, err)
  127. // uploaded a single file with the data
  128. assert.Len(t, model.ComputeAllocationCalls(), 1)
  129. assert.Len(t, model.ComputeAllocationCalls(), 1)
  130. // 2021-01-01 is already in the export file, so we only compute for 2021-01-02
  131. assert.Equal(t, time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].Start)
  132. assert.Equal(t, time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].End)
  133. assert.Equal(t, `Date,Namespace,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost,Label_app,ControllerKind,ControllerName,Pod,Container,NetworkReceiveBytes,NetworkTransferBytes,GPUs,PVBytes,NetworkCost,PVCost,GPUCost,TotalCost
  134. 2021-01-01,test-namespace,0.1,0.2,0.3,0.4,0.5,0.6,app1,,,,,,,,,,,,
  135. 2021-01-02,test-namespace,0,0,1,0,0,0,,,,,,0,0,0,0,0,0,0,1
  136. `, string(storage.Data))
  137. })
  138. t.Run("data already present in export file, export should be skipped", func(t *testing.T) {
  139. data := `Date,Name,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost
  140. 2021-01-01,test,0.1,0.2,0.3,0.4,0.5,0.6
  141. `
  142. storage := &filemanager.InMemoryFile{
  143. Data: []byte(data),
  144. }
  145. model := &AllocationModelMock{
  146. DateRangeFunc: func(_ int) (time.Time, time.Time, error) {
  147. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), nil
  148. },
  149. }
  150. err := UpdateCSV(context.TODO(), storage, model, false, nil)
  151. require.Equal(t, err, errNoData)
  152. assert.Equal(t, string(storage.Data), data)
  153. assert.Len(t, model.ComputeAllocationCalls(), 0)
  154. })
  155. t.Run("allocation data is empty", func(t *testing.T) {
  156. model := &AllocationModelMock{
  157. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*opencost.AllocationSet, error) {
  158. return &opencost.AllocationSet{
  159. Allocations: nil,
  160. }, nil
  161. },
  162. DateRangeFunc: func(_ int) (time.Time, time.Time, error) {
  163. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), nil
  164. },
  165. }
  166. storage := &filemanager.InMemoryFile{}
  167. err := UpdateCSV(context.TODO(), storage, model, false, nil)
  168. require.Equal(t, err, errNoData)
  169. })
  170. }