statsummary_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. package scrape
  2. import (
  3. "fmt"
  4. "reflect"
  5. "testing"
  6. "time"
  7. "github.com/opencost/opencost/core/pkg/clustercache"
  8. "github.com/opencost/opencost/core/pkg/source"
  9. "github.com/opencost/opencost/modules/collector-source/pkg/metric"
  10. "github.com/opencost/opencost/modules/collector-source/pkg/util"
  11. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  12. "k8s.io/apimachinery/pkg/types"
  13. stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
  14. )
  15. type mockStatSummaryClient struct {
  16. results []*stats.Summary
  17. err error
  18. }
  19. func (m *mockStatSummaryClient) GetNodeData() ([]*stats.Summary, error) {
  20. return m.results, m.err
  21. }
  22. func TestStatScraper_Scrape(t *testing.T) {
  23. start1, _ := time.Parse(time.RFC3339, Start1Str)
  24. testCache := &clustercache.MockClusterCache{
  25. Nodes: []*clustercache.Node{
  26. {Name: "node1", UID: types.UID("node-uid-1")},
  27. },
  28. PersistentVolumeClaims: []*clustercache.PersistentVolumeClaim{
  29. {Name: "pvc1", Namespace: "namespace1", UID: types.UID("pvc-uid-1")},
  30. },
  31. }
  32. tests := map[string]struct {
  33. summaries []*stats.Summary
  34. err error
  35. clusterCache *clustercache.MockClusterCache
  36. expected []metric.Update
  37. }{
  38. "nil values": {
  39. summaries: []*stats.Summary{
  40. {
  41. Node: stats.NodeStats{
  42. NodeName: "node1",
  43. CPU: &stats.CPUStats{
  44. Time: metav1.Time{Time: start1},
  45. UsageCoreNanoSeconds: nil,
  46. },
  47. Fs: &stats.FsStats{
  48. Time: metav1.Time{Time: start1},
  49. CapacityBytes: nil,
  50. },
  51. },
  52. Pods: []stats.PodStats{
  53. {
  54. PodRef: stats.PodReference{
  55. Name: "pod1",
  56. Namespace: "namespace1",
  57. UID: "uid1",
  58. },
  59. Network: &stats.NetworkStats{
  60. Time: metav1.Time{Time: start1},
  61. InterfaceStats: stats.InterfaceStats{
  62. RxBytes: nil,
  63. TxBytes: nil,
  64. },
  65. },
  66. VolumeStats: []stats.VolumeStats{
  67. {
  68. Name: "vol1",
  69. PVCRef: &stats.PVCReference{
  70. Namespace: "namespace1",
  71. Name: "pvc1",
  72. },
  73. FsStats: stats.FsStats{
  74. Time: metav1.Time{Time: start1},
  75. UsedBytes: nil,
  76. },
  77. },
  78. },
  79. Containers: []stats.ContainerStats{
  80. {
  81. Name: "container1",
  82. CPU: &stats.CPUStats{
  83. Time: metav1.Time{Time: start1},
  84. UsageCoreNanoSeconds: nil,
  85. },
  86. Memory: &stats.MemoryStats{
  87. Time: metav1.Time{Time: start1},
  88. WorkingSetBytes: nil,
  89. },
  90. Rootfs: &stats.FsStats{
  91. Time: metav1.Time{Time: start1},
  92. UsedBytes: nil,
  93. },
  94. },
  95. },
  96. },
  97. },
  98. },
  99. },
  100. expected: []metric.Update{},
  101. },
  102. "nil structs": {
  103. summaries: []*stats.Summary{
  104. {
  105. Node: stats.NodeStats{
  106. NodeName: "node1",
  107. CPU: nil,
  108. Fs: nil,
  109. },
  110. Pods: []stats.PodStats{
  111. {
  112. PodRef: stats.PodReference{
  113. Name: "pod1",
  114. Namespace: "namespace1",
  115. UID: "uid1",
  116. },
  117. Network: nil,
  118. VolumeStats: nil,
  119. Containers: []stats.ContainerStats{
  120. {
  121. Name: "container1",
  122. CPU: nil,
  123. Memory: nil,
  124. Rootfs: nil,
  125. },
  126. },
  127. },
  128. },
  129. },
  130. },
  131. expected: []metric.Update{},
  132. },
  133. "single node": {
  134. clusterCache: testCache,
  135. summaries: []*stats.Summary{
  136. {
  137. Node: stats.NodeStats{
  138. NodeName: "node1",
  139. CPU: &stats.CPUStats{
  140. Time: metav1.Time{Time: start1},
  141. UsageCoreNanoSeconds: util.Ptr(uint64(2000000000)),
  142. },
  143. Fs: &stats.FsStats{
  144. Time: metav1.Time{Time: start1},
  145. CapacityBytes: util.Ptr(uint64(2 * util.GB)),
  146. },
  147. },
  148. Pods: []stats.PodStats{
  149. {
  150. PodRef: stats.PodReference{
  151. Name: "pod1",
  152. Namespace: "namespace1",
  153. UID: "uid1",
  154. },
  155. Network: &stats.NetworkStats{
  156. Time: metav1.Time{Time: start1},
  157. InterfaceStats: stats.InterfaceStats{
  158. RxBytes: util.Ptr(uint64(1 * util.MB)),
  159. TxBytes: util.Ptr(uint64(2 * util.MB)),
  160. },
  161. },
  162. VolumeStats: []stats.VolumeStats{
  163. {
  164. Name: "ignoreVol1",
  165. FsStats: stats.FsStats{
  166. Time: metav1.Time{Time: start1},
  167. UsedBytes: util.Ptr(uint64(1 * util.GB)),
  168. },
  169. },
  170. {
  171. Name: "vol1",
  172. PVCRef: &stats.PVCReference{
  173. Namespace: "namespace1",
  174. Name: "pvc1",
  175. },
  176. FsStats: stats.FsStats{
  177. Time: metav1.Time{Time: start1},
  178. UsedBytes: util.Ptr(uint64(1 * util.GB)),
  179. },
  180. },
  181. },
  182. Containers: []stats.ContainerStats{
  183. {
  184. Name: "container1",
  185. CPU: &stats.CPUStats{
  186. Time: metav1.Time{Time: start1},
  187. UsageCoreNanoSeconds: util.Ptr(uint64(1000000000)),
  188. },
  189. Memory: &stats.MemoryStats{
  190. Time: metav1.Time{Time: start1},
  191. WorkingSetBytes: util.Ptr(uint64(5 * util.MB)),
  192. },
  193. Rootfs: &stats.FsStats{
  194. Time: metav1.Time{Time: start1},
  195. UsedBytes: util.Ptr(uint64(1 * util.GB)),
  196. },
  197. },
  198. },
  199. },
  200. },
  201. },
  202. },
  203. expected: []metric.Update{
  204. {
  205. Name: metric.NodeCPUSecondsTotal,
  206. Labels: map[string]string{
  207. source.KubernetesNodeLabel: "node1",
  208. source.UIDLabel: "node-uid-1",
  209. source.ModeLabel: "",
  210. },
  211. Value: 2,
  212. },
  213. {
  214. Name: metric.NodeFSCapacityBytes,
  215. Labels: map[string]string{
  216. source.InstanceLabel: "node1",
  217. source.UIDLabel: "node-uid-1",
  218. source.DeviceLabel: "local",
  219. },
  220. Value: float64(2 * util.GB),
  221. },
  222. {
  223. Name: metric.ContainerNetworkReceiveBytesTotal,
  224. Labels: map[string]string{
  225. source.UIDLabel: "uid1",
  226. source.NodeUIDLabel: "node-uid-1",
  227. source.PodLabel: "pod1",
  228. source.NamespaceLabel: "namespace1",
  229. },
  230. Value: float64(1 * util.MB),
  231. },
  232. {
  233. Name: metric.ContainerNetworkTransmitBytesTotal,
  234. Labels: map[string]string{
  235. source.UIDLabel: "uid1",
  236. source.NodeUIDLabel: "node-uid-1",
  237. source.PodLabel: "pod1",
  238. source.NamespaceLabel: "namespace1",
  239. },
  240. Value: float64(2 * util.MB),
  241. },
  242. {
  243. Name: metric.KubeletVolumeStatsUsedBytes,
  244. Labels: map[string]string{
  245. source.PVCLabel: "pvc1",
  246. source.NamespaceLabel: "namespace1",
  247. source.UIDLabel: "uid1",
  248. source.NodeUIDLabel: "node-uid-1",
  249. source.PVCUIDLabel: "pvc-uid-1",
  250. },
  251. Value: float64(1 * util.GB),
  252. },
  253. {
  254. Name: metric.ContainerCPUUsageSecondsTotal,
  255. Labels: map[string]string{
  256. source.ContainerLabel: "container1",
  257. source.PodLabel: "pod1",
  258. source.NamespaceLabel: "namespace1",
  259. source.NodeLabel: "node1",
  260. source.InstanceLabel: "node1",
  261. source.UIDLabel: "uid1",
  262. source.NodeUIDLabel: "node-uid-1",
  263. },
  264. Value: 1,
  265. },
  266. {
  267. Name: metric.ContainerMemoryWorkingSetBytes,
  268. Labels: map[string]string{
  269. source.ContainerLabel: "container1",
  270. source.PodLabel: "pod1",
  271. source.NamespaceLabel: "namespace1",
  272. source.NodeLabel: "node1",
  273. source.InstanceLabel: "node1",
  274. source.UIDLabel: "uid1",
  275. source.NodeUIDLabel: "node-uid-1",
  276. },
  277. Value: float64(5 * util.MB),
  278. },
  279. {
  280. Name: metric.ContainerFSUsageBytes,
  281. Labels: map[string]string{
  282. source.InstanceLabel: "node1",
  283. source.DeviceLabel: "local",
  284. source.UIDLabel: "uid1",
  285. source.NodeUIDLabel: "node-uid-1",
  286. source.ContainerLabel: "container1",
  287. },
  288. Value: float64(1 * util.GB),
  289. },
  290. },
  291. },
  292. "single node with error": {
  293. clusterCache: testCache,
  294. summaries: []*stats.Summary{
  295. {
  296. Node: stats.NodeStats{
  297. NodeName: "node1",
  298. CPU: &stats.CPUStats{
  299. Time: metav1.Time{Time: start1},
  300. UsageCoreNanoSeconds: util.Ptr(uint64(2000000000)),
  301. },
  302. Fs: &stats.FsStats{
  303. Time: metav1.Time{Time: start1},
  304. CapacityBytes: util.Ptr(uint64(2 * util.GB)),
  305. },
  306. },
  307. Pods: []stats.PodStats{
  308. {
  309. PodRef: stats.PodReference{
  310. Name: "pod1",
  311. Namespace: "namespace1",
  312. UID: "uid1",
  313. },
  314. Network: &stats.NetworkStats{
  315. Time: metav1.Time{Time: start1},
  316. InterfaceStats: stats.InterfaceStats{
  317. RxBytes: util.Ptr(uint64(1 * util.MB)),
  318. TxBytes: util.Ptr(uint64(2 * util.MB)),
  319. },
  320. },
  321. VolumeStats: []stats.VolumeStats{
  322. {
  323. Name: "ignoreVol1",
  324. FsStats: stats.FsStats{
  325. Time: metav1.Time{Time: start1},
  326. UsedBytes: util.Ptr(uint64(1 * util.GB)),
  327. },
  328. },
  329. {
  330. Name: "vol1",
  331. PVCRef: &stats.PVCReference{
  332. Namespace: "namespace1",
  333. Name: "pvc1",
  334. },
  335. FsStats: stats.FsStats{
  336. Time: metav1.Time{Time: start1},
  337. UsedBytes: util.Ptr(uint64(1 * util.GB)),
  338. },
  339. },
  340. },
  341. Containers: []stats.ContainerStats{
  342. {
  343. Name: "container1",
  344. CPU: &stats.CPUStats{
  345. Time: metav1.Time{Time: start1},
  346. UsageCoreNanoSeconds: util.Ptr(uint64(1000000000)),
  347. },
  348. Memory: &stats.MemoryStats{
  349. Time: metav1.Time{Time: start1},
  350. WorkingSetBytes: util.Ptr(uint64(5 * util.MB)),
  351. },
  352. Rootfs: &stats.FsStats{
  353. Time: metav1.Time{Time: start1},
  354. UsedBytes: util.Ptr(uint64(1 * util.GB)),
  355. },
  356. },
  357. },
  358. },
  359. },
  360. },
  361. },
  362. err: fmt.Errorf("failed to retrieve node-XYZ"),
  363. expected: []metric.Update{
  364. {
  365. Name: metric.NodeCPUSecondsTotal,
  366. Labels: map[string]string{
  367. source.KubernetesNodeLabel: "node1",
  368. source.UIDLabel: "node-uid-1",
  369. source.ModeLabel: "",
  370. },
  371. Value: 2,
  372. },
  373. {
  374. Name: metric.NodeFSCapacityBytes,
  375. Labels: map[string]string{
  376. source.InstanceLabel: "node1",
  377. source.UIDLabel: "node-uid-1",
  378. source.DeviceLabel: "local",
  379. },
  380. Value: float64(2 * util.GB),
  381. },
  382. {
  383. Name: metric.ContainerNetworkReceiveBytesTotal,
  384. Labels: map[string]string{
  385. source.UIDLabel: "uid1",
  386. source.NodeUIDLabel: "node-uid-1",
  387. source.PodLabel: "pod1",
  388. source.NamespaceLabel: "namespace1",
  389. },
  390. Value: float64(1 * util.MB),
  391. },
  392. {
  393. Name: metric.ContainerNetworkTransmitBytesTotal,
  394. Labels: map[string]string{
  395. source.UIDLabel: "uid1",
  396. source.NodeUIDLabel: "node-uid-1",
  397. source.PodLabel: "pod1",
  398. source.NamespaceLabel: "namespace1",
  399. },
  400. Value: float64(2 * util.MB),
  401. },
  402. {
  403. Name: metric.KubeletVolumeStatsUsedBytes,
  404. Labels: map[string]string{
  405. source.PVCLabel: "pvc1",
  406. source.NamespaceLabel: "namespace1",
  407. source.UIDLabel: "uid1",
  408. source.NodeUIDLabel: "node-uid-1",
  409. source.PVCUIDLabel: "pvc-uid-1",
  410. },
  411. Value: float64(1 * util.GB),
  412. },
  413. {
  414. Name: metric.ContainerCPUUsageSecondsTotal,
  415. Labels: map[string]string{
  416. source.ContainerLabel: "container1",
  417. source.PodLabel: "pod1",
  418. source.NamespaceLabel: "namespace1",
  419. source.NodeLabel: "node1",
  420. source.InstanceLabel: "node1",
  421. source.UIDLabel: "uid1",
  422. source.NodeUIDLabel: "node-uid-1",
  423. },
  424. Value: 1,
  425. },
  426. {
  427. Name: metric.ContainerMemoryWorkingSetBytes,
  428. Labels: map[string]string{
  429. source.ContainerLabel: "container1",
  430. source.PodLabel: "pod1",
  431. source.NamespaceLabel: "namespace1",
  432. source.NodeLabel: "node1",
  433. source.InstanceLabel: "node1",
  434. source.UIDLabel: "uid1",
  435. source.NodeUIDLabel: "node-uid-1",
  436. },
  437. Value: float64(5 * util.MB),
  438. },
  439. {
  440. Name: metric.ContainerFSUsageBytes,
  441. Labels: map[string]string{
  442. source.InstanceLabel: "node1",
  443. source.DeviceLabel: "local",
  444. source.UIDLabel: "uid1",
  445. source.NodeUIDLabel: "node-uid-1",
  446. source.ContainerLabel: "container1",
  447. },
  448. Value: float64(1 * util.GB),
  449. },
  450. },
  451. },
  452. "repeat pvc": {
  453. clusterCache: testCache,
  454. summaries: []*stats.Summary{
  455. {
  456. Node: stats.NodeStats{
  457. NodeName: "node1",
  458. },
  459. Pods: []stats.PodStats{
  460. {
  461. PodRef: stats.PodReference{
  462. Name: "pod1",
  463. Namespace: "namespace1",
  464. UID: "uid1",
  465. },
  466. VolumeStats: []stats.VolumeStats{
  467. {
  468. Name: "vol1",
  469. PVCRef: &stats.PVCReference{
  470. Namespace: "namespace1",
  471. Name: "pvc1",
  472. },
  473. FsStats: stats.FsStats{
  474. Time: metav1.Time{Time: start1},
  475. UsedBytes: util.Ptr(uint64(1 * util.GB)),
  476. },
  477. },
  478. },
  479. },
  480. {
  481. PodRef: stats.PodReference{
  482. Name: "pod2",
  483. Namespace: "namespace1",
  484. UID: "uid1",
  485. },
  486. VolumeStats: []stats.VolumeStats{
  487. {
  488. Name: "vol1",
  489. PVCRef: &stats.PVCReference{
  490. Namespace: "namespace1",
  491. Name: "pvc1",
  492. },
  493. FsStats: stats.FsStats{
  494. Time: metav1.Time{Time: start1},
  495. UsedBytes: util.Ptr(uint64(1 * util.GB)),
  496. },
  497. },
  498. },
  499. },
  500. },
  501. },
  502. },
  503. expected: []metric.Update{
  504. {
  505. Name: metric.KubeletVolumeStatsUsedBytes,
  506. Labels: map[string]string{
  507. source.PVCLabel: "pvc1",
  508. source.NamespaceLabel: "namespace1",
  509. source.UIDLabel: "uid1",
  510. source.NodeUIDLabel: "node-uid-1",
  511. source.PVCUIDLabel: "pvc-uid-1",
  512. },
  513. Value: float64(1 * util.GB),
  514. },
  515. },
  516. },
  517. }
  518. for name, tt := range tests {
  519. t.Run(name, func(t *testing.T) {
  520. cache := clustercache.ClusterCache(tt.clusterCache)
  521. if tt.clusterCache == nil {
  522. cache = &clustercache.MockClusterCache{}
  523. }
  524. s := &StatSummaryScraper{
  525. client: &mockStatSummaryClient{results: tt.summaries, err: tt.err},
  526. clusterCache: cache,
  527. }
  528. scrapeResults := s.Scrape()
  529. if len(scrapeResults) != len(tt.expected) {
  530. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  531. }
  532. for i, expected := range tt.expected {
  533. got := scrapeResults[i]
  534. if !reflect.DeepEqual(expected, got) {
  535. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  536. }
  537. }
  538. })
  539. }
  540. }