csv_export_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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/filemanager"
  9. "github.com/opencost/opencost/pkg/kubecost"
  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() (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) (*kubecost.AllocationSet, error) {
  21. return &kubecost.AllocationSet{
  22. Allocations: map[string]*kubecost.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[kubecost.PVKey]*kubecost.PVAllocation{
  38. kubecost.PVKey{
  39. Cluster: "test-cluster",
  40. Name: "test-pv",
  41. }: {
  42. ByteHours: 48,
  43. Cost: 2.0,
  44. },
  45. }, // 2 PVBytes, 2 PVCost
  46. Properties: &kubecost.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)
  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("merge new data with previous data (with different CSV structure)", func(t *testing.T) {
  69. storage := &filemanager.InMemoryFile{
  70. Data: []byte(`Date,Namespace,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost
  71. 2021-01-01,test-namespace,0.1,0.2,0.3,0.4,0.5,0.6
  72. `),
  73. }
  74. model := &AllocationModelMock{
  75. DateRangeFunc: func() (time.Time, time.Time, error) {
  76. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), nil
  77. },
  78. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*kubecost.AllocationSet, error) {
  79. return &kubecost.AllocationSet{
  80. Allocations: map[string]*kubecost.Allocation{
  81. "test": {
  82. Properties: &kubecost.AllocationProperties{
  83. Namespace: "test-namespace",
  84. },
  85. CPUCost: 1,
  86. },
  87. },
  88. }, nil
  89. },
  90. }
  91. err := UpdateCSV(context.TODO(), storage, model)
  92. require.NoError(t, err)
  93. // uploaded a single file with the data
  94. assert.Len(t, model.ComputeAllocationCalls(), 1)
  95. assert.Len(t, model.ComputeAllocationCalls(), 1)
  96. // 2021-01-01 is already in the export file, so we only compute for 2021-01-02
  97. assert.Equal(t, time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].Start)
  98. assert.Equal(t, time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), model.ComputeAllocationCalls()[0].End)
  99. assert.Equal(t, `Date,Namespace,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost,ControllerKind,ControllerName,Pod,Container,NetworkReceiveBytes,NetworkTransferBytes,GPUs,PVBytes,NetworkCost,PVCost,GPUCost,TotalCost
  100. 2021-01-01,test-namespace,0.1,0.2,0.3,0.4,0.5,0.6,,,,,,,,,,,,
  101. 2021-01-02,test-namespace,0,0,1,0,0,0,,,,,0,0,0,0,0,0,0,1
  102. `, string(storage.Data))
  103. })
  104. t.Run("data already present in export file, export should be skipped", func(t *testing.T) {
  105. data := `Date,Name,CPUCoreUsageAverage,CPUCoreRequestAverage,CPUCost,RAMBytesUsageAverage,RAMBytesRequestAverage,RAMCost
  106. 2021-01-01,test,0.1,0.2,0.3,0.4,0.5,0.6
  107. `
  108. storage := &filemanager.InMemoryFile{
  109. Data: []byte(data),
  110. }
  111. model := &AllocationModelMock{
  112. DateRangeFunc: func() (time.Time, time.Time, error) {
  113. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), nil
  114. },
  115. }
  116. err := UpdateCSV(context.TODO(), storage, model)
  117. require.Equal(t, err, errNoData)
  118. assert.Equal(t, string(storage.Data), data)
  119. assert.Len(t, model.ComputeAllocationCalls(), 0)
  120. })
  121. t.Run("allocation data is empty", func(t *testing.T) {
  122. model := &AllocationModelMock{
  123. ComputeAllocationFunc: func(start time.Time, end time.Time, resolution time.Duration) (*kubecost.AllocationSet, error) {
  124. return &kubecost.AllocationSet{
  125. Allocations: nil,
  126. }, nil
  127. },
  128. DateRangeFunc: func() (time.Time, time.Time, error) {
  129. return time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), nil
  130. },
  131. }
  132. storage := &filemanager.InMemoryFile{}
  133. err := UpdateCSV(context.TODO(), storage, model)
  134. require.Equal(t, err, errNoData)
  135. })
  136. }