intervals_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package costmodel
  2. import (
  3. "reflect"
  4. "testing"
  5. "time"
  6. "github.com/opencost/opencost/pkg/kubecost"
  7. )
  8. func TestGetIntervalPointsFromWindows(t *testing.T) {
  9. cases := []struct {
  10. name string
  11. pvcIntervalMap map[podKey]kubecost.Window
  12. expected []IntervalPoint
  13. }{
  14. {
  15. name: "four pods w/ various overlaps",
  16. pvcIntervalMap: map[podKey]kubecost.Window{
  17. // Pod running from 8 am to 9 am
  18. podKey{
  19. Pod: "Pod1",
  20. }: kubecost.Window(kubecost.NewClosedWindow(
  21. time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
  22. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  23. )),
  24. // Pod running from 8:30 am to 9 am
  25. podKey{
  26. Pod: "Pod2",
  27. }: kubecost.Window(kubecost.NewClosedWindow(
  28. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  29. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  30. )),
  31. // Pod running from 8:45 am to 9 am
  32. podKey{
  33. Pod: "Pod3",
  34. }: kubecost.Window(kubecost.NewClosedWindow(
  35. time.Date(2021, 2, 19, 8, 45, 0, 0, time.UTC),
  36. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  37. )),
  38. // Pod running from 8 am to 8:15 am
  39. podKey{
  40. Pod: "Pod4",
  41. }: kubecost.Window(kubecost.NewClosedWindow(
  42. time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
  43. time.Date(2021, 2, 19, 8, 15, 0, 0, time.UTC),
  44. )),
  45. },
  46. expected: []IntervalPoint{
  47. NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
  48. NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod4"}),
  49. NewIntervalPoint(time.Date(2021, 2, 19, 8, 15, 0, 0, time.UTC), "end", podKey{Pod: "Pod4"}),
  50. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
  51. NewIntervalPoint(time.Date(2021, 2, 19, 8, 45, 0, 0, time.UTC), "start", podKey{Pod: "Pod3"}),
  52. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
  53. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod3"}),
  54. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
  55. },
  56. },
  57. {
  58. name: "two pods no overlap",
  59. pvcIntervalMap: map[podKey]kubecost.Window{
  60. // Pod running from 8 am to 8:30 am
  61. podKey{
  62. Pod: "Pod1",
  63. }: kubecost.Window(kubecost.NewClosedWindow(
  64. time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
  65. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  66. )),
  67. // Pod running from 8:30 am to 9 am
  68. podKey{
  69. Pod: "Pod2",
  70. }: kubecost.Window(kubecost.NewClosedWindow(
  71. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  72. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  73. )),
  74. },
  75. expected: []IntervalPoint{
  76. NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
  77. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
  78. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
  79. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
  80. },
  81. },
  82. {
  83. name: "two pods total overlap",
  84. pvcIntervalMap: map[podKey]kubecost.Window{
  85. // Pod running from 8:30 am to 9 am
  86. podKey{
  87. Pod: "Pod1",
  88. }: kubecost.Window(kubecost.NewClosedWindow(
  89. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  90. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  91. )),
  92. // Pod running from 8:30 am to 9 am
  93. podKey{
  94. Pod: "Pod2",
  95. }: kubecost.Window(kubecost.NewClosedWindow(
  96. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  97. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  98. )),
  99. },
  100. expected: []IntervalPoint{
  101. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
  102. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
  103. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
  104. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
  105. },
  106. },
  107. {
  108. name: "one pod",
  109. pvcIntervalMap: map[podKey]kubecost.Window{
  110. // Pod running from 8 am to 9 am
  111. podKey{
  112. Pod: "Pod1",
  113. }: kubecost.Window(kubecost.NewClosedWindow(
  114. time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
  115. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  116. )),
  117. },
  118. expected: []IntervalPoint{
  119. NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
  120. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
  121. },
  122. },
  123. }
  124. for _, testCase := range cases {
  125. t.Run(testCase.name, func(t *testing.T) {
  126. result := getIntervalPointsFromWindows(testCase.pvcIntervalMap)
  127. if len(result) != len(testCase.expected) {
  128. t.Errorf("getIntervalPointsFromWindows test failed: %s: Got %+v but expected %+v", testCase.name, result, testCase.expected)
  129. }
  130. for i := range testCase.expected {
  131. // For correctness in terms of individual position of IntervalPoints, we only need to check the time/type.
  132. // Key is used in other associated calculations, so it must exist, but order does not matter if other sorting
  133. // logic is obeyed.
  134. if !testCase.expected[i].Time.Equal(result[i].Time) || testCase.expected[i].PointType != result[i].PointType {
  135. t.Errorf("getIntervalPointsFromWindows test failed: %s: Got point %s:%s but expected %s:%s", testCase.name, testCase.expected[i].PointType, testCase.expected[i].Time, result[i].PointType, result[i].Time)
  136. }
  137. }
  138. })
  139. }
  140. }
  141. func TestGetPVCCostCoefficients(t *testing.T) {
  142. cases := []struct {
  143. name string
  144. pvcIntervalMap map[podKey]kubecost.Window
  145. intervals []IntervalPoint
  146. expected map[podKey][]CoefficientComponent
  147. }{
  148. {
  149. name: "four pods w/ various overlaps",
  150. pvcIntervalMap: map[podKey]kubecost.Window{
  151. // Pod running from 8 am to 9 am
  152. podKey{
  153. Pod: "Pod1",
  154. }: kubecost.Window(kubecost.NewClosedWindow(
  155. time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
  156. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  157. )),
  158. // Pod running from 8:30 am to 9 am
  159. podKey{
  160. Pod: "Pod2",
  161. }: kubecost.Window(kubecost.NewClosedWindow(
  162. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  163. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  164. )),
  165. // Pod running from 8:45 am to 9 am
  166. podKey{
  167. Pod: "Pod3",
  168. }: kubecost.Window(kubecost.NewClosedWindow(
  169. time.Date(2021, 2, 19, 8, 45, 0, 0, time.UTC),
  170. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  171. )),
  172. // Pod running from 8 am to 8:15 am
  173. podKey{
  174. Pod: "Pod4",
  175. }: kubecost.Window(kubecost.NewClosedWindow(
  176. time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
  177. time.Date(2021, 2, 19, 8, 15, 0, 0, time.UTC),
  178. )),
  179. },
  180. intervals: []IntervalPoint{
  181. NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
  182. NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod4"}),
  183. NewIntervalPoint(time.Date(2021, 2, 19, 8, 15, 0, 0, time.UTC), "end", podKey{Pod: "Pod4"}),
  184. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
  185. NewIntervalPoint(time.Date(2021, 2, 19, 8, 45, 0, 0, time.UTC), "start", podKey{Pod: "Pod3"}),
  186. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
  187. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod3"}),
  188. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
  189. },
  190. expected: map[podKey][]CoefficientComponent{
  191. podKey{
  192. Pod: "Pod1",
  193. }: []CoefficientComponent{
  194. CoefficientComponent{0.5, 0.25},
  195. CoefficientComponent{1, 0.25},
  196. CoefficientComponent{0.5, 0.25},
  197. CoefficientComponent{1.0 / 3.0, 0.25},
  198. },
  199. podKey{
  200. Pod: "Pod2",
  201. }: []CoefficientComponent{
  202. CoefficientComponent{0.5, 0.50},
  203. CoefficientComponent{1.0 / 3.0, 0.50},
  204. },
  205. podKey{
  206. Pod: "Pod3",
  207. }: []CoefficientComponent{
  208. CoefficientComponent{1.0 / 3.0, 1.0},
  209. },
  210. podKey{
  211. Pod: "Pod4",
  212. }: []CoefficientComponent{
  213. CoefficientComponent{0.5, 1.0},
  214. },
  215. },
  216. },
  217. {
  218. name: "two pods no overlap",
  219. pvcIntervalMap: map[podKey]kubecost.Window{
  220. // Pod running from 8 am to 8:30 am
  221. podKey{
  222. Pod: "Pod1",
  223. }: kubecost.Window(kubecost.NewClosedWindow(
  224. time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
  225. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  226. )),
  227. // Pod running from 8:30 am to 9 am
  228. podKey{
  229. Pod: "Pod2",
  230. }: kubecost.Window(kubecost.NewClosedWindow(
  231. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  232. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  233. )),
  234. },
  235. intervals: []IntervalPoint{
  236. NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
  237. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
  238. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
  239. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
  240. },
  241. expected: map[podKey][]CoefficientComponent{
  242. podKey{
  243. Pod: "Pod1",
  244. }: []CoefficientComponent{
  245. CoefficientComponent{1.0, 1.0},
  246. },
  247. podKey{
  248. Pod: "Pod2",
  249. }: []CoefficientComponent{
  250. CoefficientComponent{1.0, 1.0},
  251. },
  252. },
  253. },
  254. {
  255. name: "two pods total overlap",
  256. pvcIntervalMap: map[podKey]kubecost.Window{
  257. // Pod running from 8:30 am to 9 am
  258. podKey{
  259. Pod: "Pod1",
  260. }: kubecost.Window(kubecost.NewClosedWindow(
  261. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  262. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  263. )),
  264. // Pod running from 8:30 am to 9 am
  265. podKey{
  266. Pod: "Pod2",
  267. }: kubecost.Window(kubecost.NewClosedWindow(
  268. time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
  269. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  270. )),
  271. },
  272. intervals: []IntervalPoint{
  273. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
  274. NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
  275. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
  276. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
  277. },
  278. expected: map[podKey][]CoefficientComponent{
  279. podKey{
  280. Pod: "Pod1",
  281. }: []CoefficientComponent{
  282. CoefficientComponent{0.5, 1.0},
  283. },
  284. podKey{
  285. Pod: "Pod2",
  286. }: []CoefficientComponent{
  287. CoefficientComponent{0.5, 1.0},
  288. },
  289. },
  290. },
  291. {
  292. name: "one pod",
  293. pvcIntervalMap: map[podKey]kubecost.Window{
  294. // Pod running from 8 am to 9 am
  295. podKey{
  296. Pod: "Pod1",
  297. }: kubecost.Window(kubecost.NewClosedWindow(
  298. time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
  299. time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
  300. )),
  301. },
  302. intervals: []IntervalPoint{
  303. NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
  304. NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
  305. },
  306. expected: map[podKey][]CoefficientComponent{
  307. podKey{
  308. Pod: "Pod1",
  309. }: []CoefficientComponent{
  310. CoefficientComponent{1.0, 1.0},
  311. },
  312. },
  313. },
  314. }
  315. for _, testCase := range cases {
  316. t.Run(testCase.name, func(t *testing.T) {
  317. result := getPVCCostCoefficients(testCase.intervals, testCase.pvcIntervalMap)
  318. if !reflect.DeepEqual(result, testCase.expected) {
  319. t.Errorf("getPVCCostCoefficients test failed: %s: Got %+v but expected %+v", testCase.name, result, testCase.expected)
  320. }
  321. })
  322. }
  323. }