resourcequota_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. package kubemodel
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/require"
  7. "github.com/opencost/opencost/core/pkg/model/kubemodel"
  8. "github.com/opencost/opencost/core/pkg/source"
  9. )
  10. func TestComputeResourceQuotas(t *testing.T) {
  11. start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
  12. end := start.Add(time.Hour)
  13. emptyRQ := func(uid, name, nsUID string) *kubemodel.ResourceQuota {
  14. return &kubemodel.ResourceQuota{
  15. UID: uid,
  16. Name: name,
  17. NamespaceUID: nsUID,
  18. Start: start,
  19. End: end,
  20. Spec: &kubemodel.ResourceQuotaSpec{Hard: &kubemodel.ResourceQuotaSpecHard{}},
  21. Status: &kubemodel.ResourceQuotaStatus{Used: &kubemodel.ResourceQuotaStatusUsed{}},
  22. }
  23. }
  24. tests := []struct {
  25. name string
  26. overrides map[string]any
  27. want map[string]*kubemodel.ResourceQuota
  28. }{
  29. {
  30. name: "no data returns empty resource quota map",
  31. overrides: map[string]any{},
  32. want: map[string]*kubemodel.ResourceQuota{},
  33. },
  34. {
  35. name: "basic resource quota info and uptime",
  36. overrides: map[string]any{
  37. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  38. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  39. },
  40. source.QueryResourceQuotaUptime: []*source.UptimeResult{
  41. {UID: "rq-1", First: start, Last: end},
  42. },
  43. },
  44. want: map[string]*kubemodel.ResourceQuota{
  45. "rq-1": emptyRQ("rq-1", "compute-quota", "ns-1"),
  46. },
  47. },
  48. {
  49. name: "resource quota without uptime is not registered",
  50. overrides: map[string]any{
  51. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  52. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  53. },
  54. },
  55. want: map[string]*kubemodel.ResourceQuota{},
  56. },
  57. {
  58. name: "spec cpu and ram request limits are populated",
  59. overrides: map[string]any{
  60. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  61. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  62. },
  63. source.QueryResourceQuotaUptime: []*source.UptimeResult{
  64. {UID: "rq-1", First: start, Last: end},
  65. },
  66. source.QueryResourceQuotaSpecCPURequestAverage: []*source.ResourceResult{
  67. {UID: "rq-1", Value: 2.0},
  68. },
  69. source.QueryResourceQuotaSpecCPURequestMax: []*source.ResourceResult{
  70. {UID: "rq-1", Value: 4.0},
  71. },
  72. source.QueryResourceQuotaSpecRAMRequestAverage: []*source.ResourceResult{
  73. {UID: "rq-1", Value: 4 * 1024 * 1024 * 1024},
  74. },
  75. source.QueryResourceQuotaSpecRAMRequestMax: []*source.ResourceResult{
  76. {UID: "rq-1", Value: 8 * 1024 * 1024 * 1024},
  77. },
  78. },
  79. want: map[string]*kubemodel.ResourceQuota{
  80. "rq-1": {
  81. UID: "rq-1",
  82. Name: "compute-quota",
  83. NamespaceUID: "ns-1",
  84. Start: start,
  85. End: end,
  86. Spec: &kubemodel.ResourceQuotaSpec{
  87. Hard: &kubemodel.ResourceQuotaSpecHard{
  88. Requests: kubemodel.ResourceQuantities{
  89. kubemodel.ResourceCPU: {
  90. Resource: kubemodel.ResourceCPU,
  91. Unit: kubemodel.UnitMillicore,
  92. Values: kubemodel.Stats{kubemodel.StatAvg: 2000, kubemodel.StatMax: 4000},
  93. },
  94. kubemodel.ResourceMemory: {
  95. Resource: kubemodel.ResourceMemory,
  96. Unit: kubemodel.UnitByte,
  97. Values: kubemodel.Stats{kubemodel.StatAvg: 4 * 1024 * 1024 * 1024, kubemodel.StatMax: 8 * 1024 * 1024 * 1024},
  98. },
  99. },
  100. },
  101. },
  102. Status: &kubemodel.ResourceQuotaStatus{Used: &kubemodel.ResourceQuotaStatusUsed{}},
  103. },
  104. },
  105. },
  106. {
  107. name: "status used cpu and ram are populated",
  108. overrides: map[string]any{
  109. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  110. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  111. },
  112. source.QueryResourceQuotaUptime: []*source.UptimeResult{
  113. {UID: "rq-1", First: start, Last: end},
  114. },
  115. source.QueryResourceQuotaStatusUsedCPURequestAverage: []*source.ResourceResult{
  116. {UID: "rq-1", Value: 1.0},
  117. },
  118. source.QueryResourceQuotaStatusUsedRAMRequestAverage: []*source.ResourceResult{
  119. {UID: "rq-1", Value: 2 * 1024 * 1024 * 1024},
  120. },
  121. },
  122. want: map[string]*kubemodel.ResourceQuota{
  123. "rq-1": {
  124. UID: "rq-1",
  125. Name: "compute-quota",
  126. NamespaceUID: "ns-1",
  127. Start: start,
  128. End: end,
  129. Spec: &kubemodel.ResourceQuotaSpec{Hard: &kubemodel.ResourceQuotaSpecHard{}},
  130. Status: &kubemodel.ResourceQuotaStatus{
  131. Used: &kubemodel.ResourceQuotaStatusUsed{
  132. Requests: kubemodel.ResourceQuantities{
  133. kubemodel.ResourceCPU: {
  134. Resource: kubemodel.ResourceCPU,
  135. Unit: kubemodel.UnitMillicore,
  136. Values: kubemodel.Stats{kubemodel.StatAvg: 1000},
  137. },
  138. kubemodel.ResourceMemory: {
  139. Resource: kubemodel.ResourceMemory,
  140. Unit: kubemodel.UnitByte,
  141. Values: kubemodel.Stats{kubemodel.StatAvg: 2 * 1024 * 1024 * 1024},
  142. },
  143. },
  144. },
  145. },
  146. },
  147. },
  148. },
  149. {
  150. name: "spec cpu and ram limits are populated",
  151. overrides: map[string]any{
  152. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  153. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  154. },
  155. source.QueryResourceQuotaUptime: []*source.UptimeResult{
  156. {UID: "rq-1", First: start, Last: end},
  157. },
  158. source.QueryResourceQuotaSpecCPULimitAverage: []*source.ResourceResult{
  159. {UID: "rq-1", Value: 4.0},
  160. },
  161. source.QueryResourceQuotaSpecCPULimitMax: []*source.ResourceResult{
  162. {UID: "rq-1", Value: 8.0},
  163. },
  164. source.QueryResourceQuotaSpecRAMLimitAverage: []*source.ResourceResult{
  165. {UID: "rq-1", Value: 8 * 1024 * 1024 * 1024},
  166. },
  167. source.QueryResourceQuotaSpecRAMLimitMax: []*source.ResourceResult{
  168. {UID: "rq-1", Value: 16 * 1024 * 1024 * 1024},
  169. },
  170. },
  171. want: map[string]*kubemodel.ResourceQuota{
  172. "rq-1": {
  173. UID: "rq-1",
  174. Name: "compute-quota",
  175. NamespaceUID: "ns-1",
  176. Start: start,
  177. End: end,
  178. Spec: &kubemodel.ResourceQuotaSpec{
  179. Hard: &kubemodel.ResourceQuotaSpecHard{
  180. Limits: kubemodel.ResourceQuantities{
  181. kubemodel.ResourceCPU: {
  182. Resource: kubemodel.ResourceCPU,
  183. Unit: kubemodel.UnitMillicore,
  184. Values: kubemodel.Stats{kubemodel.StatAvg: 4000, kubemodel.StatMax: 8000},
  185. },
  186. kubemodel.ResourceMemory: {
  187. Resource: kubemodel.ResourceMemory,
  188. Unit: kubemodel.UnitByte,
  189. Values: kubemodel.Stats{kubemodel.StatAvg: 8 * 1024 * 1024 * 1024, kubemodel.StatMax: 16 * 1024 * 1024 * 1024},
  190. },
  191. },
  192. },
  193. },
  194. Status: &kubemodel.ResourceQuotaStatus{Used: &kubemodel.ResourceQuotaStatusUsed{}},
  195. },
  196. },
  197. },
  198. {
  199. name: "status used cpu and ram request max are populated",
  200. overrides: map[string]any{
  201. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  202. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  203. },
  204. source.QueryResourceQuotaUptime: []*source.UptimeResult{
  205. {UID: "rq-1", First: start, Last: end},
  206. },
  207. source.QueryResourceQuotaStatusUsedCPURequestMax: []*source.ResourceResult{
  208. {UID: "rq-1", Value: 3.0},
  209. },
  210. source.QueryResourceQuotaStatusUsedRAMRequestMax: []*source.ResourceResult{
  211. {UID: "rq-1", Value: 6 * 1024 * 1024 * 1024},
  212. },
  213. },
  214. want: map[string]*kubemodel.ResourceQuota{
  215. "rq-1": {
  216. UID: "rq-1",
  217. Name: "compute-quota",
  218. NamespaceUID: "ns-1",
  219. Start: start,
  220. End: end,
  221. Spec: &kubemodel.ResourceQuotaSpec{Hard: &kubemodel.ResourceQuotaSpecHard{}},
  222. Status: &kubemodel.ResourceQuotaStatus{
  223. Used: &kubemodel.ResourceQuotaStatusUsed{
  224. Requests: kubemodel.ResourceQuantities{
  225. kubemodel.ResourceCPU: {
  226. Resource: kubemodel.ResourceCPU,
  227. Unit: kubemodel.UnitMillicore,
  228. Values: kubemodel.Stats{kubemodel.StatMax: 3000},
  229. },
  230. kubemodel.ResourceMemory: {
  231. Resource: kubemodel.ResourceMemory,
  232. Unit: kubemodel.UnitByte,
  233. Values: kubemodel.Stats{kubemodel.StatMax: 6 * 1024 * 1024 * 1024},
  234. },
  235. },
  236. },
  237. },
  238. },
  239. },
  240. },
  241. {
  242. name: "status used cpu and ram limits are populated",
  243. overrides: map[string]any{
  244. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  245. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  246. },
  247. source.QueryResourceQuotaUptime: []*source.UptimeResult{
  248. {UID: "rq-1", First: start, Last: end},
  249. },
  250. source.QueryResourceQuotaStatusUsedCPULimitAverage: []*source.ResourceResult{
  251. {UID: "rq-1", Value: 2.0},
  252. },
  253. source.QueryResourceQuotaStatusUsedCPULimitMax: []*source.ResourceResult{
  254. {UID: "rq-1", Value: 4.0},
  255. },
  256. source.QueryResourceQuotaStatusUsedRAMLimitAverage: []*source.ResourceResult{
  257. {UID: "rq-1", Value: 4 * 1024 * 1024 * 1024},
  258. },
  259. source.QueryResourceQuotaStatusUsedRAMLimitMax: []*source.ResourceResult{
  260. {UID: "rq-1", Value: 8 * 1024 * 1024 * 1024},
  261. },
  262. },
  263. want: map[string]*kubemodel.ResourceQuota{
  264. "rq-1": {
  265. UID: "rq-1",
  266. Name: "compute-quota",
  267. NamespaceUID: "ns-1",
  268. Start: start,
  269. End: end,
  270. Spec: &kubemodel.ResourceQuotaSpec{Hard: &kubemodel.ResourceQuotaSpecHard{}},
  271. Status: &kubemodel.ResourceQuotaStatus{
  272. Used: &kubemodel.ResourceQuotaStatusUsed{
  273. Limits: kubemodel.ResourceQuantities{
  274. kubemodel.ResourceCPU: {
  275. Resource: kubemodel.ResourceCPU,
  276. Unit: kubemodel.UnitMillicore,
  277. Values: kubemodel.Stats{kubemodel.StatAvg: 2000, kubemodel.StatMax: 4000},
  278. },
  279. kubemodel.ResourceMemory: {
  280. Resource: kubemodel.ResourceMemory,
  281. Unit: kubemodel.UnitByte,
  282. Values: kubemodel.Stats{kubemodel.StatAvg: 4 * 1024 * 1024 * 1024, kubemodel.StatMax: 8 * 1024 * 1024 * 1024},
  283. },
  284. },
  285. },
  286. },
  287. },
  288. },
  289. },
  290. {
  291. name: "metric data for unknown resource quota is ignored",
  292. overrides: map[string]any{
  293. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  294. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  295. },
  296. source.QueryResourceQuotaUptime: []*source.UptimeResult{
  297. {UID: "rq-1", First: start, Last: end},
  298. },
  299. // all 16 metric queries carry an unknown UID — hit every !ok warn path
  300. source.QueryResourceQuotaSpecCPURequestAverage: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  301. source.QueryResourceQuotaSpecCPURequestMax: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  302. source.QueryResourceQuotaSpecRAMRequestAverage: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  303. source.QueryResourceQuotaSpecRAMRequestMax: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  304. source.QueryResourceQuotaSpecCPULimitAverage: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  305. source.QueryResourceQuotaSpecCPULimitMax: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  306. source.QueryResourceQuotaSpecRAMLimitAverage: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  307. source.QueryResourceQuotaSpecRAMLimitMax: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  308. source.QueryResourceQuotaStatusUsedCPURequestAverage: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  309. source.QueryResourceQuotaStatusUsedCPURequestMax: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  310. source.QueryResourceQuotaStatusUsedRAMRequestAverage: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  311. source.QueryResourceQuotaStatusUsedRAMRequestMax: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  312. source.QueryResourceQuotaStatusUsedCPULimitAverage: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  313. source.QueryResourceQuotaStatusUsedCPULimitMax: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  314. source.QueryResourceQuotaStatusUsedRAMLimitAverage: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  315. source.QueryResourceQuotaStatusUsedRAMLimitMax: []*source.ResourceResult{{UID: "unknown-rq", Value: 1.0}},
  316. },
  317. want: map[string]*kubemodel.ResourceQuota{
  318. "rq-1": emptyRQ("rq-1", "compute-quota", "ns-1"),
  319. },
  320. },
  321. {
  322. name: "uptime for unknown resource quota is ignored",
  323. overrides: map[string]any{
  324. source.QueryResourceQuotaInfo: []*source.ResourceQuotaInfoResult{
  325. {UID: "rq-1", ResourceQuota: "compute-quota", NamespaceUID: "ns-1"},
  326. },
  327. source.QueryResourceQuotaUptime: []*source.UptimeResult{
  328. {UID: "rq-1", First: start, Last: end},
  329. {UID: "unknown-rq", First: start, Last: end},
  330. },
  331. },
  332. want: map[string]*kubemodel.ResourceQuota{
  333. "rq-1": emptyRQ("rq-1", "compute-quota", "ns-1"),
  334. },
  335. },
  336. }
  337. for _, tt := range tests {
  338. t.Run(tt.name, func(t *testing.T) {
  339. ds := source.NewMockOpenCostDataSource()
  340. ds.ResolutionValue = 5 * time.Minute
  341. seedCluster(ds, start, end)
  342. for method, result := range tt.overrides {
  343. ds.Querier.SetOverride(method, result)
  344. }
  345. km, err := NewKubeModel(testClusterUID, false, ds)
  346. require.NoError(t, err)
  347. kms, err := km.ComputeKubeModelSet(start, end)
  348. require.NoError(t, err)
  349. assert.Equal(t, tt.want, kms.ResourceQuotas)
  350. })
  351. }
  352. }