allocation_json_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. package opencost
  2. import (
  3. "encoding/json"
  4. "math"
  5. "testing"
  6. "time"
  7. "github.com/opencost/opencost/core/pkg/util/mathutil"
  8. )
  9. func TestAllocation_MarshalJSON(t *testing.T) {
  10. start := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  11. end := time.Date(2021, time.January, 2, 0, 0, 0, 0, time.UTC)
  12. hrs := 24.0
  13. gib := 1024.0 * 1024.0 * 1024.0
  14. cpuPrice := 0.02
  15. gpuPrice := 2.00
  16. ramPrice := 0.01
  17. pvPrice := 0.00005
  18. before := &Allocation{
  19. Name: "cluster1/namespace1/node1/pod1/container1",
  20. Properties: &AllocationProperties{
  21. Cluster: "cluster1",
  22. Node: "node1",
  23. Namespace: "namespace1",
  24. Pod: "pod1",
  25. Container: "container1",
  26. },
  27. Window: NewWindow(&start, &end),
  28. Start: start,
  29. End: end,
  30. CPUCoreHours: 2.0 * hrs,
  31. CPUCoreRequestAverage: 2.0,
  32. CPUCoreUsageAverage: 1.0,
  33. CPUCost: 2.0 * hrs * cpuPrice,
  34. CPUCostAdjustment: 3.0,
  35. GPUHours: 1.0 * hrs,
  36. GPUCost: 1.0 * hrs * gpuPrice,
  37. GPUCostAdjustment: 2.0,
  38. NetworkCost: 0.05,
  39. LoadBalancerCost: 0.02,
  40. PVs: PVAllocations{
  41. disk: {
  42. ByteHours: 100.0 * gib * hrs,
  43. Cost: 100.0 * hrs * pvPrice,
  44. },
  45. },
  46. PVCostAdjustment: 4.0,
  47. RAMByteHours: 8.0 * gib * hrs,
  48. RAMBytesRequestAverage: 8.0 * gib,
  49. RAMBytesUsageAverage: 4.0 * gib,
  50. RAMCost: 8.0 * hrs * ramPrice,
  51. RAMCostAdjustment: 1.0,
  52. SharedCost: 2.00,
  53. ExternalCost: 1.00,
  54. RawAllocationOnly: &RawAllocationOnlyData{},
  55. }
  56. data, err := json.Marshal(before)
  57. if err != nil {
  58. t.Fatalf("Allocation.MarshalJSON: unexpected error: %s", err)
  59. }
  60. after := &Allocation{}
  61. err = json.Unmarshal(data, after)
  62. if err != nil {
  63. t.Fatalf("Allocation.UnmarshalJSON: unexpected error: %s", err)
  64. }
  65. // TODO:CLEANUP fix json marshaling of Window so that all of this works.
  66. // In the meantime, just set the Window so that we can test the rest.
  67. after.Window = before.Window.Clone()
  68. if !after.Equal(before) {
  69. t.Fatalf("Allocation.MarshalJSON: before and after are not equal")
  70. }
  71. }
  72. func TestPVAllocations_MarshalJSON(t *testing.T) {
  73. testCases := map[string]PVAllocations{
  74. "empty": {},
  75. "single": {
  76. {
  77. Cluster: "cluster1",
  78. Name: "pv1",
  79. }: {
  80. ByteHours: 100,
  81. Cost: 1,
  82. },
  83. },
  84. "multi": {
  85. {
  86. Cluster: "cluster1",
  87. Name: "pv1",
  88. }: {
  89. ByteHours: 100,
  90. Cost: 1,
  91. },
  92. {
  93. Cluster: "cluster1",
  94. Name: "pv2",
  95. }: {
  96. ByteHours: 200,
  97. Cost: 2,
  98. },
  99. },
  100. "emptyPV": {
  101. {
  102. Cluster: "cluster1",
  103. Name: "pv1",
  104. }: {},
  105. },
  106. "emptyKey": {
  107. {}: {
  108. ByteHours: 100,
  109. Cost: 1,
  110. },
  111. },
  112. }
  113. for name, before := range testCases {
  114. t.Run(name, func(t *testing.T) {
  115. data, err := json.Marshal(before)
  116. if err != nil {
  117. t.Fatalf("PVAllocations.MarshalJSON: unexpected error: %s", err)
  118. }
  119. after := PVAllocations{}
  120. err = json.Unmarshal(data, &after)
  121. if err != nil {
  122. t.Fatalf("PVAllocations.UnmarshalJSON: unexpected error: %s", err)
  123. }
  124. if len(before) != len(after) {
  125. t.Fatalf("PVAllocations.MarshalJSON: before and after are not equal")
  126. }
  127. for pvKey, beforePV := range before {
  128. afterPV, ok := after[pvKey]
  129. if !ok {
  130. t.Fatalf("PVAllocations.MarshalJSON: after missing PVKey %s", pvKey)
  131. }
  132. if beforePV.Cost != afterPV.Cost {
  133. t.Fatalf("PVAllocations.MarshalJSON: PVAllocation Cost not equal for PVKey %s", pvKey)
  134. }
  135. if beforePV.ByteHours != afterPV.ByteHours {
  136. t.Fatalf("PVAllocations.MarshalJSON: PVAllocation ByteHours not equal for PVKey %s", pvKey)
  137. }
  138. }
  139. })
  140. }
  141. }
  142. func TestLbAllocation_MarshalJSON(t *testing.T) {
  143. testCases := map[string]LbAllocations{
  144. "empty": {},
  145. "single": {
  146. "cluster1/namespace1/ingress": {
  147. Service: "namespace1/ingress",
  148. Cost: 1,
  149. Private: false,
  150. Ip: "127.0.0.1",
  151. },
  152. },
  153. "multi": {
  154. "cluster1/namespace1/ingress": {
  155. Service: "namespace1/ingress",
  156. Cost: 1,
  157. Private: false,
  158. Ip: "127.0.0.1",
  159. },
  160. "cluster1/namespace1/frontend": {
  161. Service: "namespace1/frontend",
  162. Cost: 1,
  163. Private: false,
  164. Ip: "127.0.0.2",
  165. },
  166. },
  167. "emptyLB": {
  168. "cluster1/namespace1/pod": {},
  169. },
  170. }
  171. for name, before := range testCases {
  172. t.Run(name, func(t *testing.T) {
  173. data, err := json.Marshal(before)
  174. if err != nil {
  175. t.Fatalf("LbAllocations.MarshalJSON: unexpected error: %s", err)
  176. }
  177. after := LbAllocations{}
  178. err = json.Unmarshal(data, &after)
  179. if err != nil {
  180. t.Fatalf("LbAllocations.UnmarshalJSON: unexpected error: %s", err)
  181. }
  182. if len(before) != len(after) {
  183. t.Fatalf("LbAllocations.MarshalJSON: before and after are not equal")
  184. }
  185. for serviceKey, beforeLB := range before {
  186. afterLB, ok := after[serviceKey]
  187. if !ok {
  188. t.Fatalf("LbAllocations.MarshalJSON: after missing serviceKey %s", serviceKey)
  189. }
  190. if beforeLB.Cost != afterLB.Cost {
  191. t.Fatalf("LbAllocations.MarshalJSON: LbAllocation Cost not equal for serviceKey %s", serviceKey)
  192. }
  193. if beforeLB.Ip != afterLB.Ip {
  194. t.Fatalf("LbAllocations.MarshalJSON: LbAllocation Ip not equal for serviceKey %s", serviceKey)
  195. }
  196. }
  197. })
  198. }
  199. }
  200. func TestFormatFloat64ForResponse(t *testing.T) {
  201. type formatTestCase struct {
  202. name string
  203. input float64
  204. expectedNil bool
  205. expectedValue float64
  206. }
  207. testCases := []formatTestCase{
  208. {
  209. name: "zero",
  210. input: 0.0,
  211. expectedNil: false,
  212. expectedValue: 0.0,
  213. },
  214. {
  215. name: "round to zero",
  216. input: 0.000000001,
  217. expectedNil: false,
  218. expectedValue: 0,
  219. },
  220. {
  221. name: "valid value, no rounding",
  222. input: 14.123456,
  223. expectedNil: false,
  224. expectedValue: 14.123456,
  225. },
  226. {
  227. name: "valid value, with rounding",
  228. input: 14.1234567,
  229. expectedNil: false,
  230. expectedValue: 14.123457,
  231. },
  232. {
  233. name: "NaN is nil",
  234. input: math.NaN(),
  235. expectedNil: true,
  236. },
  237. {
  238. name: "infinite is nil",
  239. input: math.Inf(1),
  240. expectedNil: true,
  241. },
  242. {
  243. name: "negative infinite is nil",
  244. input: math.Inf(-1),
  245. expectedNil: true,
  246. },
  247. }
  248. for _, tc := range testCases {
  249. result := formatFloat64ForResponse(tc.input)
  250. if result == nil && tc.expectedNil == false {
  251. t.Fatalf("test case: %s: expected a value %f, got nil instead", tc.name, tc.expectedValue)
  252. }
  253. if result != nil && tc.expectedNil == true {
  254. t.Fatalf("test case: %s: expected nil, got value %f instead", tc.name, *result)
  255. }
  256. if result != nil && !mathutil.Approximately(*result, tc.expectedValue) {
  257. t.Fatalf("test case: %s: expected %f, got %f", tc.name, tc.expectedValue, *result)
  258. }
  259. }
  260. }