csv_export_test.go 7.7 KB

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