clustercache_test.go 84 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094
  1. package scrape
  2. import (
  3. "reflect"
  4. "testing"
  5. "time"
  6. "github.com/opencost/opencost/core/pkg/clustercache"
  7. "github.com/opencost/opencost/core/pkg/external"
  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. v1 "k8s.io/api/core/v1"
  12. "k8s.io/apimachinery/pkg/api/resource"
  13. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  14. "k8s.io/apimachinery/pkg/types"
  15. )
  16. var Start1Str = "2025-01-01T00:00:00Z00:00"
  17. func Test_kubernetesScraper_scrapeNodes(t *testing.T) {
  18. start1, _ := time.Parse(time.RFC3339, Start1Str)
  19. type scrape struct {
  20. Nodes []*clustercache.Node
  21. Timestamp time.Time
  22. }
  23. tests := []struct {
  24. name string
  25. scrapes []scrape
  26. expected []metric.Update
  27. }{
  28. {
  29. name: "simple",
  30. scrapes: []scrape{
  31. {
  32. Nodes: []*clustercache.Node{
  33. {
  34. Name: "node1",
  35. UID: "uuid1",
  36. SpecProviderID: "i-1",
  37. Status: v1.NodeStatus{
  38. Capacity: v1.ResourceList{
  39. v1.ResourceCPU: resource.MustParse("2"),
  40. v1.ResourceMemory: resource.MustParse("2048"),
  41. },
  42. Allocatable: v1.ResourceList{
  43. v1.ResourceCPU: resource.MustParse("1"),
  44. v1.ResourceMemory: resource.MustParse("1024"),
  45. },
  46. },
  47. Labels: map[string]string{
  48. "test1": "blah",
  49. "test2": "blah2",
  50. },
  51. },
  52. },
  53. Timestamp: start1,
  54. },
  55. },
  56. expected: []metric.Update{
  57. {
  58. Name: metric.NodeInfo,
  59. Labels: map[string]string{
  60. source.NodeLabel: "node1",
  61. source.ProviderIDLabel: "i-1",
  62. source.UIDLabel: "uuid1",
  63. },
  64. Value: 0,
  65. AdditionalInfo: map[string]string{
  66. source.NodeLabel: "node1",
  67. source.ProviderIDLabel: "i-1",
  68. source.UIDLabel: "uuid1",
  69. },
  70. },
  71. {
  72. Name: metric.NodeResourceCapacities,
  73. Labels: map[string]string{
  74. source.NodeLabel: "node1",
  75. source.ProviderIDLabel: "i-1",
  76. source.UIDLabel: "uuid1",
  77. source.ResourceLabel: "cpu",
  78. source.UnitLabel: "core",
  79. },
  80. Value: 2.0,
  81. AdditionalInfo: nil,
  82. },
  83. {
  84. Name: metric.NodeResourceCapacities,
  85. Labels: map[string]string{
  86. source.NodeLabel: "node1",
  87. source.ProviderIDLabel: "i-1",
  88. source.UIDLabel: "uuid1",
  89. source.ResourceLabel: "memory",
  90. source.UnitLabel: "byte",
  91. },
  92. Value: 2048.0,
  93. AdditionalInfo: nil,
  94. },
  95. {
  96. Name: metric.KubeNodeStatusCapacityCPUCores,
  97. Labels: map[string]string{
  98. source.NodeLabel: "node1",
  99. source.ProviderIDLabel: "i-1",
  100. source.UIDLabel: "uuid1",
  101. },
  102. Value: 2.0,
  103. AdditionalInfo: nil,
  104. },
  105. {
  106. Name: metric.KubeNodeStatusCapacityMemoryBytes,
  107. Labels: map[string]string{
  108. source.NodeLabel: "node1",
  109. source.ProviderIDLabel: "i-1",
  110. source.UIDLabel: "uuid1",
  111. },
  112. Value: 2048.0,
  113. AdditionalInfo: nil,
  114. },
  115. {
  116. Name: metric.NodeResourcesAllocatable,
  117. Labels: map[string]string{
  118. source.NodeLabel: "node1",
  119. source.ProviderIDLabel: "i-1",
  120. source.UIDLabel: "uuid1",
  121. source.ResourceLabel: "cpu",
  122. source.UnitLabel: "core",
  123. },
  124. Value: 1.0,
  125. AdditionalInfo: nil,
  126. },
  127. {
  128. Name: metric.NodeResourcesAllocatable,
  129. Labels: map[string]string{
  130. source.NodeLabel: "node1",
  131. source.ProviderIDLabel: "i-1",
  132. source.UIDLabel: "uuid1",
  133. source.ResourceLabel: "memory",
  134. source.UnitLabel: "byte",
  135. },
  136. Value: 1024.0,
  137. AdditionalInfo: nil,
  138. },
  139. {
  140. Name: metric.KubeNodeStatusAllocatableCPUCores,
  141. Labels: map[string]string{
  142. source.NodeLabel: "node1",
  143. source.ProviderIDLabel: "i-1",
  144. source.UIDLabel: "uuid1",
  145. },
  146. Value: 1.0,
  147. AdditionalInfo: nil,
  148. },
  149. {
  150. Name: metric.KubeNodeStatusAllocatableMemoryBytes,
  151. Labels: map[string]string{
  152. source.NodeLabel: "node1",
  153. source.ProviderIDLabel: "i-1",
  154. source.UIDLabel: "uuid1",
  155. },
  156. Value: 1024.0,
  157. AdditionalInfo: nil,
  158. },
  159. {
  160. Name: metric.KubeNodeLabels,
  161. Labels: map[string]string{
  162. source.NodeLabel: "node1",
  163. source.ProviderIDLabel: "i-1",
  164. source.UIDLabel: "uuid1",
  165. },
  166. Value: 0,
  167. AdditionalInfo: map[string]string{
  168. "label_test1": "blah",
  169. "label_test2": "blah2",
  170. },
  171. },
  172. },
  173. },
  174. }
  175. for _, tt := range tests {
  176. t.Run(tt.name, func(t *testing.T) {
  177. ks := &ClusterCacheScraper{}
  178. var scrapeResults []metric.Update
  179. for _, s := range tt.scrapes {
  180. res := ks.scrapeNodes(s.Nodes)
  181. scrapeResults = append(scrapeResults, res...)
  182. }
  183. if len(scrapeResults) != len(tt.expected) {
  184. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  185. }
  186. for i, expected := range tt.expected {
  187. got := scrapeResults[i]
  188. if !reflect.DeepEqual(expected, got) {
  189. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  190. }
  191. }
  192. })
  193. }
  194. }
  195. func Test_kubernetesScraper_scrapeNodesWithExternalLabels(t *testing.T) {
  196. start1, _ := time.Parse(time.RFC3339, Start1Str)
  197. const (
  198. testSource = "mock"
  199. testExternalLabelKey = "externalLabelKey"
  200. testExternalLabelValue = "externalLabelValue"
  201. )
  202. mockLabelProvider := external.NewNodeLabelProvider()
  203. err := mockLabelProvider.Update(testSource, map[string]string{testExternalLabelKey: testExternalLabelValue})
  204. if err != nil {
  205. t.Fatalf("failed to get test node labels: %s", err)
  206. }
  207. type scrape struct {
  208. Nodes []*clustercache.Node
  209. Timestamp time.Time
  210. }
  211. tests := []struct {
  212. name string
  213. scrapes []scrape
  214. expected []metric.Update
  215. }{
  216. {
  217. name: "simple",
  218. scrapes: []scrape{
  219. {
  220. Nodes: []*clustercache.Node{
  221. {
  222. Name: "node1",
  223. UID: "uuid1",
  224. SpecProviderID: "i-1",
  225. Status: v1.NodeStatus{
  226. Capacity: v1.ResourceList{
  227. v1.ResourceCPU: resource.MustParse("2"),
  228. v1.ResourceMemory: resource.MustParse("2048"),
  229. },
  230. Allocatable: v1.ResourceList{
  231. v1.ResourceCPU: resource.MustParse("1"),
  232. v1.ResourceMemory: resource.MustParse("1024"),
  233. },
  234. },
  235. Labels: map[string]string{
  236. "test1": "blah",
  237. "test2": "blah2",
  238. },
  239. },
  240. },
  241. Timestamp: start1,
  242. },
  243. },
  244. expected: []metric.Update{
  245. {
  246. Name: metric.NodeInfo,
  247. Labels: map[string]string{
  248. source.NodeLabel: "node1",
  249. source.ProviderIDLabel: "i-1",
  250. source.UIDLabel: "uuid1",
  251. },
  252. Value: 0,
  253. AdditionalInfo: map[string]string{
  254. source.NodeLabel: "node1",
  255. source.ProviderIDLabel: "i-1",
  256. source.UIDLabel: "uuid1",
  257. },
  258. },
  259. {
  260. Name: metric.NodeResourceCapacities,
  261. Labels: map[string]string{
  262. source.NodeLabel: "node1",
  263. source.ProviderIDLabel: "i-1",
  264. source.UIDLabel: "uuid1",
  265. source.ResourceLabel: "cpu",
  266. source.UnitLabel: "core",
  267. },
  268. Value: 2.0,
  269. AdditionalInfo: nil,
  270. },
  271. {
  272. Name: metric.NodeResourceCapacities,
  273. Labels: map[string]string{
  274. source.NodeLabel: "node1",
  275. source.ProviderIDLabel: "i-1",
  276. source.UIDLabel: "uuid1",
  277. source.ResourceLabel: "memory",
  278. source.UnitLabel: "byte",
  279. },
  280. Value: 2048.0,
  281. AdditionalInfo: nil,
  282. },
  283. {
  284. Name: metric.KubeNodeStatusCapacityCPUCores,
  285. Labels: map[string]string{
  286. source.NodeLabel: "node1",
  287. source.ProviderIDLabel: "i-1",
  288. source.UIDLabel: "uuid1",
  289. },
  290. Value: 2.0,
  291. AdditionalInfo: nil,
  292. },
  293. {
  294. Name: metric.KubeNodeStatusCapacityMemoryBytes,
  295. Labels: map[string]string{
  296. source.NodeLabel: "node1",
  297. source.ProviderIDLabel: "i-1",
  298. source.UIDLabel: "uuid1",
  299. },
  300. Value: 2048.0,
  301. AdditionalInfo: nil,
  302. },
  303. {
  304. Name: metric.NodeResourcesAllocatable,
  305. Labels: map[string]string{
  306. source.NodeLabel: "node1",
  307. source.ProviderIDLabel: "i-1",
  308. source.UIDLabel: "uuid1",
  309. source.ResourceLabel: "cpu",
  310. source.UnitLabel: "core",
  311. },
  312. Value: 1.0,
  313. AdditionalInfo: nil,
  314. },
  315. {
  316. Name: metric.NodeResourcesAllocatable,
  317. Labels: map[string]string{
  318. source.NodeLabel: "node1",
  319. source.ProviderIDLabel: "i-1",
  320. source.UIDLabel: "uuid1",
  321. source.ResourceLabel: "memory",
  322. source.UnitLabel: "byte",
  323. },
  324. Value: 1024.0,
  325. AdditionalInfo: nil,
  326. },
  327. {
  328. Name: metric.KubeNodeStatusAllocatableCPUCores,
  329. Labels: map[string]string{
  330. source.NodeLabel: "node1",
  331. source.ProviderIDLabel: "i-1",
  332. source.UIDLabel: "uuid1",
  333. },
  334. Value: 1.0,
  335. AdditionalInfo: nil,
  336. },
  337. {
  338. Name: metric.KubeNodeStatusAllocatableMemoryBytes,
  339. Labels: map[string]string{
  340. source.NodeLabel: "node1",
  341. source.ProviderIDLabel: "i-1",
  342. source.UIDLabel: "uuid1",
  343. },
  344. Value: 1024.0,
  345. AdditionalInfo: nil,
  346. },
  347. {
  348. Name: metric.KubeNodeLabels,
  349. Labels: map[string]string{
  350. source.NodeLabel: "node1",
  351. source.ProviderIDLabel: "i-1",
  352. source.UIDLabel: "uuid1",
  353. },
  354. Value: 0,
  355. AdditionalInfo: map[string]string{
  356. "label_test1": "blah",
  357. "label_test2": "blah2",
  358. // need label key with prefix label_ so the decoder does not exclude.
  359. "label_" + testExternalLabelKey: testExternalLabelValue,
  360. },
  361. },
  362. },
  363. },
  364. }
  365. for _, tt := range tests {
  366. t.Run(tt.name, func(t *testing.T) {
  367. ks := &ClusterCacheScraper{
  368. externalLabelProvider: mockLabelProvider,
  369. }
  370. var scrapeResults []metric.Update
  371. for _, s := range tt.scrapes {
  372. res := ks.scrapeNodes(s.Nodes)
  373. scrapeResults = append(scrapeResults, res...)
  374. }
  375. if len(scrapeResults) != len(tt.expected) {
  376. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  377. }
  378. for i, expected := range tt.expected {
  379. got := scrapeResults[i]
  380. if !reflect.DeepEqual(expected, got) {
  381. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  382. }
  383. }
  384. })
  385. }
  386. }
  387. func Test_kubernetesScraper_scrapeDeployments(t *testing.T) {
  388. start1, _ := time.Parse(time.RFC3339, Start1Str)
  389. type scrape struct {
  390. Deployments []*clustercache.Deployment
  391. Timestamp time.Time
  392. }
  393. tests := []struct {
  394. name string
  395. scrapes []scrape
  396. expected []metric.Update
  397. }{
  398. {
  399. name: "simple",
  400. scrapes: []scrape{
  401. {
  402. Deployments: []*clustercache.Deployment{
  403. {
  404. Name: "deployment1",
  405. Namespace: "namespace1",
  406. UID: "uuid1",
  407. MatchLabels: map[string]string{
  408. "test1": "blah",
  409. "test2": "blah2",
  410. },
  411. },
  412. },
  413. Timestamp: start1,
  414. },
  415. },
  416. // deploymentInfo map is shared across all 4 metrics and has namespace
  417. // added to it before DeploymentMatchLabels is appended, so all 4 metrics
  418. // reflect the final state: {uid, namespace_uid, deployment, namespace}.
  419. expected: []metric.Update{
  420. {
  421. Name: metric.DeploymentInfo,
  422. Labels: map[string]string{
  423. source.UIDLabel: "uuid1",
  424. source.NamespaceUIDLabel: "ns-uuid1",
  425. source.DeploymentLabel: "deployment1",
  426. source.NamespaceLabel: "namespace1",
  427. },
  428. Value: 0,
  429. AdditionalInfo: map[string]string{
  430. source.UIDLabel: "uuid1",
  431. source.NamespaceUIDLabel: "ns-uuid1",
  432. source.DeploymentLabel: "deployment1",
  433. source.NamespaceLabel: "namespace1",
  434. },
  435. },
  436. {
  437. Name: metric.DeploymentLabels,
  438. Labels: map[string]string{
  439. source.UIDLabel: "uuid1",
  440. source.NamespaceUIDLabel: "ns-uuid1",
  441. source.DeploymentLabel: "deployment1",
  442. source.NamespaceLabel: "namespace1",
  443. },
  444. Value: 0,
  445. AdditionalInfo: map[string]string{},
  446. },
  447. {
  448. Name: metric.DeploymentAnnotations,
  449. Labels: map[string]string{
  450. source.UIDLabel: "uuid1",
  451. source.NamespaceUIDLabel: "ns-uuid1",
  452. source.DeploymentLabel: "deployment1",
  453. source.NamespaceLabel: "namespace1",
  454. },
  455. Value: 0,
  456. AdditionalInfo: map[string]string{},
  457. },
  458. {
  459. Name: metric.DeploymentMatchLabels,
  460. Labels: map[string]string{
  461. source.UIDLabel: "uuid1",
  462. source.NamespaceUIDLabel: "ns-uuid1",
  463. source.DeploymentLabel: "deployment1",
  464. source.NamespaceLabel: "namespace1",
  465. },
  466. Value: 0,
  467. AdditionalInfo: map[string]string{
  468. "label_test1": "blah",
  469. "label_test2": "blah2",
  470. },
  471. },
  472. },
  473. },
  474. }
  475. for _, tt := range tests {
  476. t.Run(tt.name, func(t *testing.T) {
  477. ks := &ClusterCacheScraper{}
  478. nsIndex := make(map[string]types.UID, 0)
  479. nsIndex["namespace1"] = "ns-uuid1"
  480. var scrapeResults []metric.Update
  481. for _, s := range tt.scrapes {
  482. res := ks.scrapeDeployments(s.Deployments, nsIndex)
  483. scrapeResults = append(scrapeResults, res...)
  484. }
  485. if len(scrapeResults) != len(tt.expected) {
  486. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  487. }
  488. for i, expected := range tt.expected {
  489. got := scrapeResults[i]
  490. if !reflect.DeepEqual(expected, got) {
  491. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  492. }
  493. }
  494. })
  495. }
  496. }
  497. func Test_kubernetesScraper_scrapeNamespaces(t *testing.T) {
  498. start1, _ := time.Parse(time.RFC3339, Start1Str)
  499. type scrape struct {
  500. Namespaces []*clustercache.Namespace
  501. Timestamp time.Time
  502. }
  503. tests := []struct {
  504. name string
  505. scrapes []scrape
  506. expected []metric.Update
  507. }{
  508. {
  509. name: "simple",
  510. scrapes: []scrape{
  511. {
  512. Namespaces: []*clustercache.Namespace{
  513. {
  514. Name: "namespace1",
  515. UID: "uuid1",
  516. Labels: map[string]string{
  517. "test1": "blah",
  518. "test2": "blah2",
  519. },
  520. Annotations: map[string]string{
  521. "test3": "blah3",
  522. "test4": "blah4",
  523. },
  524. },
  525. },
  526. Timestamp: start1,
  527. },
  528. },
  529. expected: []metric.Update{
  530. {
  531. Name: metric.NamespaceInfo,
  532. Labels: map[string]string{
  533. source.NamespaceLabel: "namespace1",
  534. source.UIDLabel: "uuid1",
  535. },
  536. Value: 0,
  537. AdditionalInfo: map[string]string{
  538. source.NamespaceLabel: "namespace1",
  539. source.UIDLabel: "uuid1",
  540. },
  541. },
  542. {
  543. Name: metric.KubeNamespaceLabels,
  544. Labels: map[string]string{
  545. source.NamespaceLabel: "namespace1",
  546. source.UIDLabel: "uuid1",
  547. },
  548. Value: 0,
  549. AdditionalInfo: map[string]string{
  550. "label_test1": "blah",
  551. "label_test2": "blah2",
  552. },
  553. },
  554. {
  555. Name: metric.KubeNamespaceAnnotations,
  556. Labels: map[string]string{
  557. source.NamespaceLabel: "namespace1",
  558. source.UIDLabel: "uuid1",
  559. },
  560. Value: 0,
  561. AdditionalInfo: map[string]string{
  562. "annotation_test3": "blah3",
  563. "annotation_test4": "blah4",
  564. },
  565. },
  566. },
  567. },
  568. }
  569. for _, tt := range tests {
  570. t.Run(tt.name, func(t *testing.T) {
  571. ks := &ClusterCacheScraper{}
  572. var scrapeResults []metric.Update
  573. for _, s := range tt.scrapes {
  574. res := ks.scrapeNamespaces(s.Namespaces)
  575. scrapeResults = append(scrapeResults, res...)
  576. }
  577. if len(scrapeResults) != len(tt.expected) {
  578. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  579. }
  580. for i, expected := range tt.expected {
  581. got := scrapeResults[i]
  582. if !reflect.DeepEqual(expected, got) {
  583. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  584. }
  585. }
  586. })
  587. }
  588. }
  589. func Test_kubernetesScraper_scrapePods(t *testing.T) {
  590. start1, _ := time.Parse(time.RFC3339, Start1Str)
  591. type scrape struct {
  592. PVCs []*clustercache.PersistentVolumeClaim
  593. Pods []*clustercache.Pod
  594. Timestamp time.Time
  595. }
  596. tests := []struct {
  597. name string
  598. nsSetup func(map[string]types.UID)
  599. scrapes []scrape
  600. expected []metric.Update
  601. }{
  602. {
  603. name: "simple",
  604. scrapes: []scrape{
  605. {
  606. PVCs: []*clustercache.PersistentVolumeClaim{
  607. {
  608. Name: "pvc1",
  609. Namespace: "namespace1",
  610. UID: "uuid1",
  611. Spec: v1.PersistentVolumeClaimSpec{
  612. VolumeName: "vol1",
  613. StorageClassName: util.Ptr("storageClass1"),
  614. Resources: v1.VolumeResourceRequirements{
  615. Requests: v1.ResourceList{
  616. v1.ResourceStorage: resource.MustParse("4096"),
  617. },
  618. },
  619. },
  620. },
  621. },
  622. Pods: []*clustercache.Pod{
  623. {
  624. Name: "pod1",
  625. Namespace: "namespace1",
  626. UID: "uuid1",
  627. Spec: clustercache.PodSpec{
  628. NodeName: "node1",
  629. Containers: []clustercache.Container{
  630. {
  631. Name: "container1",
  632. Resources: v1.ResourceRequirements{
  633. Requests: map[v1.ResourceName]resource.Quantity{
  634. v1.ResourceCPU: resource.MustParse("500m"),
  635. v1.ResourceMemory: resource.MustParse("512"),
  636. },
  637. Limits: map[v1.ResourceName]resource.Quantity{
  638. v1.ResourceCPU: resource.MustParse("1"),
  639. v1.ResourceMemory: resource.MustParse("1024"),
  640. },
  641. },
  642. },
  643. },
  644. },
  645. Labels: map[string]string{
  646. "test1": "blah",
  647. "test2": "blah2",
  648. },
  649. Annotations: map[string]string{
  650. "test3": "blah3",
  651. "test4": "blah4",
  652. },
  653. OwnerReferences: []metav1.OwnerReference{
  654. {
  655. Kind: source.DeploymentLabel,
  656. Name: "deployment1",
  657. Controller: nil,
  658. },
  659. },
  660. Status: clustercache.PodStatus{
  661. ContainerStatuses: []v1.ContainerStatus{
  662. {
  663. Name: "container1",
  664. State: v1.ContainerState{
  665. Running: &v1.ContainerStateRunning{},
  666. },
  667. },
  668. },
  669. },
  670. },
  671. },
  672. Timestamp: start1,
  673. },
  674. },
  675. // podInfo is mutated after PodInfo is appended (namespace/node/instance added),
  676. // so PodInfo.Labels also reflects those fields at assertion time.
  677. expected: []metric.Update{
  678. {
  679. Name: metric.PodInfo,
  680. Labels: map[string]string{
  681. source.UIDLabel: "uuid1",
  682. source.PodLabel: "pod1",
  683. source.NamespaceUIDLabel: "",
  684. source.NodeUIDLabel: "",
  685. source.NamespaceLabel: "namespace1",
  686. source.NodeLabel: "node1",
  687. source.InstanceLabel: "node1",
  688. },
  689. Value: 0,
  690. AdditionalInfo: map[string]string{
  691. source.UIDLabel: "uuid1",
  692. source.PodLabel: "pod1",
  693. source.NamespaceUIDLabel: "",
  694. source.NodeUIDLabel: "",
  695. source.NamespaceLabel: "namespace1",
  696. source.NodeLabel: "node1",
  697. source.InstanceLabel: "node1",
  698. },
  699. },
  700. {
  701. Name: metric.KubePodLabels,
  702. Labels: map[string]string{
  703. source.UIDLabel: "uuid1",
  704. source.PodLabel: "pod1",
  705. source.NamespaceUIDLabel: "",
  706. source.NodeUIDLabel: "",
  707. source.NamespaceLabel: "namespace1",
  708. source.NodeLabel: "node1",
  709. source.InstanceLabel: "node1",
  710. },
  711. Value: 0,
  712. AdditionalInfo: map[string]string{
  713. "label_test1": "blah",
  714. "label_test2": "blah2",
  715. },
  716. },
  717. {
  718. Name: metric.KubePodAnnotations,
  719. Labels: map[string]string{
  720. source.UIDLabel: "uuid1",
  721. source.PodLabel: "pod1",
  722. source.NamespaceUIDLabel: "",
  723. source.NodeUIDLabel: "",
  724. source.NamespaceLabel: "namespace1",
  725. source.NodeLabel: "node1",
  726. source.InstanceLabel: "node1",
  727. },
  728. Value: 0,
  729. AdditionalInfo: map[string]string{
  730. "annotation_test3": "blah3",
  731. "annotation_test4": "blah4",
  732. },
  733. },
  734. {
  735. Name: metric.KubePodOwner,
  736. Labels: map[string]string{
  737. source.UIDLabel: "uuid1",
  738. source.PodLabel: "pod1",
  739. source.NamespaceUIDLabel: "",
  740. source.NodeUIDLabel: "",
  741. source.NamespaceLabel: "namespace1",
  742. source.NodeLabel: "node1",
  743. source.InstanceLabel: "node1",
  744. source.OwnerKindLabel: "deployment",
  745. source.OwnerNameLabel: "deployment1",
  746. source.OwnerUIDLabel: "",
  747. source.ControllerLabel: "false",
  748. },
  749. Value: 0,
  750. AdditionalInfo: map[string]string{
  751. source.UIDLabel: "uuid1",
  752. source.PodLabel: "pod1",
  753. source.NamespaceUIDLabel: "",
  754. source.NodeUIDLabel: "",
  755. source.NamespaceLabel: "namespace1",
  756. source.NodeLabel: "node1",
  757. source.InstanceLabel: "node1",
  758. source.OwnerKindLabel: "deployment",
  759. source.OwnerNameLabel: "deployment1",
  760. source.OwnerUIDLabel: "",
  761. source.ControllerLabel: "false",
  762. },
  763. },
  764. {
  765. Name: metric.KubePodContainerStatusRunning,
  766. Labels: map[string]string{
  767. source.UIDLabel: "uuid1",
  768. source.PodLabel: "pod1",
  769. source.NamespaceUIDLabel: "",
  770. source.NodeUIDLabel: "",
  771. source.NamespaceLabel: "namespace1",
  772. source.NodeLabel: "node1",
  773. source.InstanceLabel: "node1",
  774. source.ContainerLabel: "container1",
  775. },
  776. Value: 0,
  777. AdditionalInfo: map[string]string{
  778. source.UIDLabel: "uuid1",
  779. source.PodLabel: "pod1",
  780. source.NamespaceUIDLabel: "",
  781. source.NodeUIDLabel: "",
  782. source.NamespaceLabel: "namespace1",
  783. source.NodeLabel: "node1",
  784. source.InstanceLabel: "node1",
  785. source.ContainerLabel: "container1",
  786. },
  787. },
  788. {
  789. Name: metric.KubePodContainerResourceRequests,
  790. Labels: map[string]string{
  791. source.UIDLabel: "uuid1",
  792. source.PodLabel: "pod1",
  793. source.NamespaceUIDLabel: "",
  794. source.NodeUIDLabel: "",
  795. source.NamespaceLabel: "namespace1",
  796. source.NodeLabel: "node1",
  797. source.InstanceLabel: "node1",
  798. source.ContainerLabel: "container1",
  799. source.ResourceLabel: "cpu",
  800. source.UnitLabel: "core",
  801. },
  802. Value: 0.5,
  803. AdditionalInfo: nil,
  804. },
  805. {
  806. Name: metric.KubePodContainerResourceRequests,
  807. Labels: map[string]string{
  808. source.UIDLabel: "uuid1",
  809. source.PodLabel: "pod1",
  810. source.NamespaceUIDLabel: "",
  811. source.NodeUIDLabel: "",
  812. source.NamespaceLabel: "namespace1",
  813. source.NodeLabel: "node1",
  814. source.InstanceLabel: "node1",
  815. source.ContainerLabel: "container1",
  816. source.ResourceLabel: "memory",
  817. source.UnitLabel: "byte",
  818. },
  819. Value: 512,
  820. AdditionalInfo: nil,
  821. },
  822. {
  823. Name: metric.KubePodContainerResourceLimits,
  824. Labels: map[string]string{
  825. source.UIDLabel: "uuid1",
  826. source.PodLabel: "pod1",
  827. source.NamespaceUIDLabel: "",
  828. source.NodeUIDLabel: "",
  829. source.NamespaceLabel: "namespace1",
  830. source.NodeLabel: "node1",
  831. source.InstanceLabel: "node1",
  832. source.ContainerLabel: "container1",
  833. source.ResourceLabel: "cpu",
  834. source.UnitLabel: "core",
  835. },
  836. Value: 1,
  837. AdditionalInfo: nil,
  838. },
  839. {
  840. Name: metric.KubePodContainerResourceLimits,
  841. Labels: map[string]string{
  842. source.UIDLabel: "uuid1",
  843. source.PodLabel: "pod1",
  844. source.NamespaceUIDLabel: "",
  845. source.NodeUIDLabel: "",
  846. source.NamespaceLabel: "namespace1",
  847. source.NodeLabel: "node1",
  848. source.InstanceLabel: "node1",
  849. source.ContainerLabel: "container1",
  850. source.ResourceLabel: "memory",
  851. source.UnitLabel: "byte",
  852. },
  853. Value: 1024,
  854. AdditionalInfo: nil,
  855. },
  856. {
  857. Name: metric.PodPVCAllocation,
  858. Labels: map[string]string{
  859. source.PodLabel: unmountedPVsContainer,
  860. source.NamespaceLabel: "namespace1",
  861. source.PVCLabel: "pvc1",
  862. source.PVLabel: "vol1",
  863. },
  864. Value: 4096,
  865. AdditionalInfo: nil,
  866. },
  867. },
  868. },
  869. {
  870. name: "with namespace index",
  871. nsSetup: func(nsIndex map[string]types.UID) {
  872. nsIndex["namespace1"] = "ns-uuid1"
  873. },
  874. scrapes: []scrape{
  875. {
  876. Pods: []*clustercache.Pod{
  877. {
  878. Name: "pod1",
  879. Namespace: "namespace1",
  880. UID: "uuid1",
  881. Spec: clustercache.PodSpec{
  882. NodeName: "node1",
  883. Containers: []clustercache.Container{
  884. {
  885. Name: "container1",
  886. Resources: v1.ResourceRequirements{
  887. Requests: map[v1.ResourceName]resource.Quantity{
  888. v1.ResourceCPU: resource.MustParse("500m"),
  889. v1.ResourceMemory: resource.MustParse("512"),
  890. },
  891. Limits: map[v1.ResourceName]resource.Quantity{
  892. v1.ResourceCPU: resource.MustParse("1"),
  893. v1.ResourceMemory: resource.MustParse("1024"),
  894. },
  895. },
  896. },
  897. },
  898. },
  899. Status: clustercache.PodStatus{
  900. ContainerStatuses: []v1.ContainerStatus{
  901. {
  902. Name: "container1",
  903. State: v1.ContainerState{
  904. Running: &v1.ContainerStateRunning{},
  905. },
  906. },
  907. },
  908. },
  909. },
  910. },
  911. Timestamp: start1,
  912. },
  913. },
  914. expected: []metric.Update{
  915. {
  916. Name: metric.PodInfo,
  917. Labels: map[string]string{
  918. source.UIDLabel: "uuid1",
  919. source.PodLabel: "pod1",
  920. source.NamespaceUIDLabel: "ns-uuid1",
  921. source.NodeUIDLabel: "",
  922. source.NamespaceLabel: "namespace1",
  923. source.NodeLabel: "node1",
  924. source.InstanceLabel: "node1",
  925. },
  926. Value: 0,
  927. AdditionalInfo: map[string]string{
  928. source.UIDLabel: "uuid1",
  929. source.PodLabel: "pod1",
  930. source.NamespaceUIDLabel: "ns-uuid1",
  931. source.NodeUIDLabel: "",
  932. source.NamespaceLabel: "namespace1",
  933. source.NodeLabel: "node1",
  934. source.InstanceLabel: "node1",
  935. },
  936. },
  937. {
  938. Name: metric.KubePodLabels,
  939. Labels: map[string]string{
  940. source.UIDLabel: "uuid1",
  941. source.PodLabel: "pod1",
  942. source.NamespaceUIDLabel: "ns-uuid1",
  943. source.NodeUIDLabel: "",
  944. source.NamespaceLabel: "namespace1",
  945. source.NodeLabel: "node1",
  946. source.InstanceLabel: "node1",
  947. },
  948. Value: 0,
  949. AdditionalInfo: map[string]string{},
  950. },
  951. {
  952. Name: metric.KubePodAnnotations,
  953. Labels: map[string]string{
  954. source.UIDLabel: "uuid1",
  955. source.PodLabel: "pod1",
  956. source.NamespaceUIDLabel: "ns-uuid1",
  957. source.NodeUIDLabel: "",
  958. source.NamespaceLabel: "namespace1",
  959. source.NodeLabel: "node1",
  960. source.InstanceLabel: "node1",
  961. },
  962. Value: 0,
  963. AdditionalInfo: map[string]string{},
  964. },
  965. {
  966. Name: metric.KubePodContainerStatusRunning,
  967. Labels: map[string]string{
  968. source.UIDLabel: "uuid1",
  969. source.PodLabel: "pod1",
  970. source.NamespaceUIDLabel: "ns-uuid1",
  971. source.NodeUIDLabel: "",
  972. source.NamespaceLabel: "namespace1",
  973. source.NodeLabel: "node1",
  974. source.InstanceLabel: "node1",
  975. source.ContainerLabel: "container1",
  976. },
  977. Value: 0,
  978. AdditionalInfo: map[string]string{
  979. source.UIDLabel: "uuid1",
  980. source.PodLabel: "pod1",
  981. source.NamespaceUIDLabel: "ns-uuid1",
  982. source.NodeUIDLabel: "",
  983. source.NamespaceLabel: "namespace1",
  984. source.NodeLabel: "node1",
  985. source.InstanceLabel: "node1",
  986. source.ContainerLabel: "container1",
  987. },
  988. },
  989. {
  990. Name: metric.KubePodContainerResourceRequests,
  991. Labels: map[string]string{
  992. source.UIDLabel: "uuid1",
  993. source.PodLabel: "pod1",
  994. source.NamespaceUIDLabel: "ns-uuid1",
  995. source.NodeUIDLabel: "",
  996. source.NamespaceLabel: "namespace1",
  997. source.NodeLabel: "node1",
  998. source.InstanceLabel: "node1",
  999. source.ContainerLabel: "container1",
  1000. source.ResourceLabel: "cpu",
  1001. source.UnitLabel: "core",
  1002. },
  1003. Value: 0.5,
  1004. AdditionalInfo: nil,
  1005. },
  1006. {
  1007. Name: metric.KubePodContainerResourceRequests,
  1008. Labels: map[string]string{
  1009. source.UIDLabel: "uuid1",
  1010. source.PodLabel: "pod1",
  1011. source.NamespaceUIDLabel: "ns-uuid1",
  1012. source.NodeUIDLabel: "",
  1013. source.NamespaceLabel: "namespace1",
  1014. source.NodeLabel: "node1",
  1015. source.InstanceLabel: "node1",
  1016. source.ContainerLabel: "container1",
  1017. source.ResourceLabel: "memory",
  1018. source.UnitLabel: "byte",
  1019. },
  1020. Value: 512,
  1021. AdditionalInfo: nil,
  1022. },
  1023. {
  1024. Name: metric.KubePodContainerResourceLimits,
  1025. Labels: map[string]string{
  1026. source.UIDLabel: "uuid1",
  1027. source.PodLabel: "pod1",
  1028. source.NamespaceUIDLabel: "ns-uuid1",
  1029. source.NodeUIDLabel: "",
  1030. source.NamespaceLabel: "namespace1",
  1031. source.NodeLabel: "node1",
  1032. source.InstanceLabel: "node1",
  1033. source.ContainerLabel: "container1",
  1034. source.ResourceLabel: "cpu",
  1035. source.UnitLabel: "core",
  1036. },
  1037. Value: 1,
  1038. AdditionalInfo: nil,
  1039. },
  1040. {
  1041. Name: metric.KubePodContainerResourceLimits,
  1042. Labels: map[string]string{
  1043. source.UIDLabel: "uuid1",
  1044. source.PodLabel: "pod1",
  1045. source.NamespaceUIDLabel: "ns-uuid1",
  1046. source.NodeUIDLabel: "",
  1047. source.NamespaceLabel: "namespace1",
  1048. source.NodeLabel: "node1",
  1049. source.InstanceLabel: "node1",
  1050. source.ContainerLabel: "container1",
  1051. source.ResourceLabel: "memory",
  1052. source.UnitLabel: "byte",
  1053. },
  1054. Value: 1024,
  1055. AdditionalInfo: nil,
  1056. },
  1057. },
  1058. },
  1059. }
  1060. for _, tt := range tests {
  1061. t.Run(tt.name, func(t *testing.T) {
  1062. ks := &ClusterCacheScraper{}
  1063. nodeIndex := make(map[string]types.UID, 0)
  1064. nsIndex := make(map[string]types.UID, 0)
  1065. if tt.nsSetup != nil {
  1066. tt.nsSetup(nsIndex)
  1067. }
  1068. pvcIndex := make(map[pvcKey]types.UID, 0)
  1069. var scrapeResults []metric.Update
  1070. for _, s := range tt.scrapes {
  1071. res := ks.scrapePods(s.Pods, s.PVCs, nodeIndex, nsIndex, pvcIndex)
  1072. scrapeResults = append(scrapeResults, res...)
  1073. }
  1074. if len(scrapeResults) != len(tt.expected) {
  1075. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  1076. }
  1077. for i, expected := range tt.expected {
  1078. got := scrapeResults[i]
  1079. if !reflect.DeepEqual(expected, got) {
  1080. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  1081. }
  1082. }
  1083. })
  1084. }
  1085. }
  1086. func Test_kubernetesScraper_scrapePVCs(t *testing.T) {
  1087. start1, _ := time.Parse(time.RFC3339, Start1Str)
  1088. type scrape struct {
  1089. PVCs []*clustercache.PersistentVolumeClaim
  1090. Timestamp time.Time
  1091. }
  1092. tests := []struct {
  1093. name string
  1094. nsSetup func(map[string]types.UID)
  1095. scrapes []scrape
  1096. expected []metric.Update
  1097. }{
  1098. {
  1099. name: "simple",
  1100. scrapes: []scrape{
  1101. {
  1102. PVCs: []*clustercache.PersistentVolumeClaim{
  1103. {
  1104. Name: "pvc1",
  1105. Namespace: "namespace1",
  1106. UID: "uuid1",
  1107. Spec: v1.PersistentVolumeClaimSpec{
  1108. VolumeName: "vol1",
  1109. StorageClassName: util.Ptr("storageClass1"),
  1110. Resources: v1.VolumeResourceRequirements{
  1111. Requests: v1.ResourceList{
  1112. v1.ResourceStorage: resource.MustParse("4096"),
  1113. },
  1114. },
  1115. },
  1116. },
  1117. },
  1118. Timestamp: start1,
  1119. },
  1120. },
  1121. expected: []metric.Update{
  1122. {
  1123. Name: metric.KubePersistentVolumeClaimInfo,
  1124. Labels: map[string]string{
  1125. source.UIDLabel: "uuid1",
  1126. source.PVCLabel: "pvc1",
  1127. source.NamespaceUIDLabel: "",
  1128. source.NamespaceLabel: "namespace1",
  1129. source.VolumeNameLabel: "vol1",
  1130. source.PVUIDLabel: "",
  1131. source.StorageClassLabel: "storageClass1",
  1132. },
  1133. Value: 0,
  1134. AdditionalInfo: map[string]string{
  1135. source.UIDLabel: "uuid1",
  1136. source.PVCLabel: "pvc1",
  1137. source.NamespaceUIDLabel: "",
  1138. source.NamespaceLabel: "namespace1",
  1139. source.VolumeNameLabel: "vol1",
  1140. source.PVUIDLabel: "",
  1141. source.StorageClassLabel: "storageClass1",
  1142. },
  1143. },
  1144. {
  1145. Name: metric.KubePersistentVolumeClaimResourceRequestsStorageBytes,
  1146. Labels: map[string]string{
  1147. source.UIDLabel: "uuid1",
  1148. source.PVCLabel: "pvc1",
  1149. source.NamespaceUIDLabel: "",
  1150. source.NamespaceLabel: "namespace1",
  1151. source.VolumeNameLabel: "vol1",
  1152. source.PVUIDLabel: "",
  1153. source.StorageClassLabel: "storageClass1",
  1154. },
  1155. Value: 4096,
  1156. AdditionalInfo: nil,
  1157. },
  1158. },
  1159. },
  1160. {
  1161. name: "with namespace index",
  1162. nsSetup: func(nsIndex map[string]types.UID) {
  1163. nsIndex["namespace1"] = "ns-uuid1"
  1164. },
  1165. scrapes: []scrape{
  1166. {
  1167. PVCs: []*clustercache.PersistentVolumeClaim{
  1168. {
  1169. Name: "pvc1",
  1170. Namespace: "namespace1",
  1171. UID: "uuid1",
  1172. Spec: v1.PersistentVolumeClaimSpec{
  1173. VolumeName: "vol1",
  1174. StorageClassName: util.Ptr("storageClass1"),
  1175. Resources: v1.VolumeResourceRequirements{
  1176. Requests: v1.ResourceList{
  1177. v1.ResourceStorage: resource.MustParse("4096"),
  1178. },
  1179. },
  1180. },
  1181. },
  1182. },
  1183. Timestamp: start1,
  1184. },
  1185. },
  1186. expected: []metric.Update{
  1187. {
  1188. Name: metric.KubePersistentVolumeClaimInfo,
  1189. Labels: map[string]string{
  1190. source.UIDLabel: "uuid1",
  1191. source.PVCLabel: "pvc1",
  1192. source.NamespaceUIDLabel: "ns-uuid1",
  1193. source.NamespaceLabel: "namespace1",
  1194. source.VolumeNameLabel: "vol1",
  1195. source.PVUIDLabel: "",
  1196. source.StorageClassLabel: "storageClass1",
  1197. },
  1198. Value: 0,
  1199. AdditionalInfo: map[string]string{
  1200. source.UIDLabel: "uuid1",
  1201. source.PVCLabel: "pvc1",
  1202. source.NamespaceUIDLabel: "ns-uuid1",
  1203. source.NamespaceLabel: "namespace1",
  1204. source.VolumeNameLabel: "vol1",
  1205. source.PVUIDLabel: "",
  1206. source.StorageClassLabel: "storageClass1",
  1207. },
  1208. },
  1209. {
  1210. Name: metric.KubePersistentVolumeClaimResourceRequestsStorageBytes,
  1211. Labels: map[string]string{
  1212. source.UIDLabel: "uuid1",
  1213. source.PVCLabel: "pvc1",
  1214. source.NamespaceUIDLabel: "ns-uuid1",
  1215. source.NamespaceLabel: "namespace1",
  1216. source.VolumeNameLabel: "vol1",
  1217. source.PVUIDLabel: "",
  1218. source.StorageClassLabel: "storageClass1",
  1219. },
  1220. Value: 4096,
  1221. AdditionalInfo: nil,
  1222. },
  1223. },
  1224. },
  1225. }
  1226. for _, tt := range tests {
  1227. t.Run(tt.name, func(t *testing.T) {
  1228. ks := &ClusterCacheScraper{}
  1229. nsIndex := make(map[string]types.UID, 0)
  1230. if tt.nsSetup != nil {
  1231. tt.nsSetup(nsIndex)
  1232. }
  1233. pvIndex := make(map[string]types.UID, 0)
  1234. var scrapeResults []metric.Update
  1235. for _, s := range tt.scrapes {
  1236. res := ks.scrapePVCs(s.PVCs, nsIndex, pvIndex)
  1237. scrapeResults = append(scrapeResults, res...)
  1238. }
  1239. if len(scrapeResults) != len(tt.expected) {
  1240. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  1241. }
  1242. for i, expected := range tt.expected {
  1243. got := scrapeResults[i]
  1244. if !reflect.DeepEqual(expected, got) {
  1245. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  1246. }
  1247. }
  1248. })
  1249. }
  1250. }
  1251. func Test_kubernetesScraper_scrapePVs(t *testing.T) {
  1252. start1, _ := time.Parse(time.RFC3339, Start1Str)
  1253. type scrape struct {
  1254. PVs []*clustercache.PersistentVolume
  1255. Timestamp time.Time
  1256. }
  1257. tests := []struct {
  1258. name string
  1259. scrapes []scrape
  1260. expected []metric.Update
  1261. }{
  1262. {
  1263. name: "simple",
  1264. scrapes: []scrape{
  1265. {
  1266. PVs: []*clustercache.PersistentVolume{
  1267. {
  1268. Name: "pv1",
  1269. UID: "uuid1",
  1270. Spec: v1.PersistentVolumeSpec{
  1271. StorageClassName: "storageClass1",
  1272. PersistentVolumeSource: v1.PersistentVolumeSource{
  1273. CSI: &v1.CSIPersistentVolumeSource{
  1274. VolumeHandle: "vol-1",
  1275. },
  1276. },
  1277. Capacity: v1.ResourceList{
  1278. v1.ResourceStorage: resource.MustParse("4096"),
  1279. },
  1280. },
  1281. },
  1282. },
  1283. Timestamp: start1,
  1284. },
  1285. },
  1286. expected: []metric.Update{
  1287. {
  1288. Name: metric.KubecostPVInfo,
  1289. Labels: map[string]string{
  1290. source.UIDLabel: "uuid1",
  1291. source.PVLabel: "pv1",
  1292. source.StorageClassLabel: "storageClass1",
  1293. source.ProviderIDLabel: "vol-1",
  1294. source.CSIVolumeHandleLabel: "vol-1",
  1295. },
  1296. Value: 0,
  1297. AdditionalInfo: map[string]string{
  1298. source.UIDLabel: "uuid1",
  1299. source.PVLabel: "pv1",
  1300. source.StorageClassLabel: "storageClass1",
  1301. source.ProviderIDLabel: "vol-1",
  1302. source.CSIVolumeHandleLabel: "vol-1",
  1303. },
  1304. },
  1305. {
  1306. Name: metric.KubePersistentVolumeCapacityBytes,
  1307. Labels: map[string]string{
  1308. source.UIDLabel: "uuid1",
  1309. source.PVLabel: "pv1",
  1310. source.StorageClassLabel: "storageClass1",
  1311. source.ProviderIDLabel: "vol-1",
  1312. source.CSIVolumeHandleLabel: "vol-1",
  1313. },
  1314. Value: 4096,
  1315. AdditionalInfo: nil,
  1316. },
  1317. },
  1318. },
  1319. {
  1320. // Non-CSI PV: provider ID comes from the in-tree AWS EBS source and
  1321. // the csi_volume_handle label must not be present at all.
  1322. name: "aws ebs non-csi pv omits csi_volume_handle label",
  1323. scrapes: []scrape{
  1324. {
  1325. PVs: []*clustercache.PersistentVolume{
  1326. {
  1327. Name: "pv-aws",
  1328. UID: "uuid-aws",
  1329. Spec: v1.PersistentVolumeSpec{
  1330. StorageClassName: "gp2",
  1331. PersistentVolumeSource: v1.PersistentVolumeSource{
  1332. AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
  1333. VolumeID: "aws://us-east-2a/vol-0fc54c5e83b8d2b76",
  1334. },
  1335. },
  1336. Capacity: v1.ResourceList{
  1337. v1.ResourceStorage: resource.MustParse("8192"),
  1338. },
  1339. },
  1340. },
  1341. },
  1342. Timestamp: start1,
  1343. },
  1344. },
  1345. expected: []metric.Update{
  1346. {
  1347. Name: metric.KubecostPVInfo,
  1348. Labels: map[string]string{
  1349. source.UIDLabel: "uuid-aws",
  1350. source.PVLabel: "pv-aws",
  1351. source.StorageClassLabel: "gp2",
  1352. source.ProviderIDLabel: "vol-0fc54c5e83b8d2b76",
  1353. },
  1354. Value: 0,
  1355. AdditionalInfo: map[string]string{
  1356. source.UIDLabel: "uuid-aws",
  1357. source.PVLabel: "pv-aws",
  1358. source.StorageClassLabel: "gp2",
  1359. source.ProviderIDLabel: "vol-0fc54c5e83b8d2b76",
  1360. },
  1361. },
  1362. {
  1363. Name: metric.KubePersistentVolumeCapacityBytes,
  1364. Labels: map[string]string{
  1365. source.UIDLabel: "uuid-aws",
  1366. source.PVLabel: "pv-aws",
  1367. source.StorageClassLabel: "gp2",
  1368. source.ProviderIDLabel: "vol-0fc54c5e83b8d2b76",
  1369. },
  1370. Value: 8192,
  1371. AdditionalInfo: nil,
  1372. },
  1373. },
  1374. },
  1375. {
  1376. // GCE PD in-tree source: provider ID is the PD name, no csi label.
  1377. name: "gce pd non-csi pv omits csi_volume_handle label",
  1378. scrapes: []scrape{
  1379. {
  1380. PVs: []*clustercache.PersistentVolume{
  1381. {
  1382. Name: "pv-gce",
  1383. UID: "uuid-gce",
  1384. Spec: v1.PersistentVolumeSpec{
  1385. StorageClassName: "standard",
  1386. PersistentVolumeSource: v1.PersistentVolumeSource{
  1387. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
  1388. PDName: "gke-pvc-abc123",
  1389. },
  1390. },
  1391. },
  1392. },
  1393. },
  1394. Timestamp: start1,
  1395. },
  1396. },
  1397. expected: []metric.Update{
  1398. {
  1399. Name: metric.KubecostPVInfo,
  1400. Labels: map[string]string{
  1401. source.UIDLabel: "uuid-gce",
  1402. source.PVLabel: "pv-gce",
  1403. source.StorageClassLabel: "standard",
  1404. source.ProviderIDLabel: "gke-pvc-abc123",
  1405. },
  1406. Value: 0,
  1407. AdditionalInfo: map[string]string{
  1408. source.UIDLabel: "uuid-gce",
  1409. source.PVLabel: "pv-gce",
  1410. source.StorageClassLabel: "standard",
  1411. source.ProviderIDLabel: "gke-pvc-abc123",
  1412. },
  1413. },
  1414. },
  1415. },
  1416. {
  1417. // CSI source present but VolumeHandle empty: csi_volume_handle label
  1418. // is omitted, and provider_id currently ends up empty (documents the
  1419. // gap where the old code fell back to pv.Name).
  1420. name: "csi pv with empty volume handle omits csi label and has empty provider id",
  1421. scrapes: []scrape{
  1422. {
  1423. PVs: []*clustercache.PersistentVolume{
  1424. {
  1425. Name: "pv-csi-empty",
  1426. UID: "uuid-csi-empty",
  1427. Spec: v1.PersistentVolumeSpec{
  1428. StorageClassName: "gp3",
  1429. PersistentVolumeSource: v1.PersistentVolumeSource{
  1430. CSI: &v1.CSIPersistentVolumeSource{
  1431. VolumeHandle: "",
  1432. },
  1433. },
  1434. },
  1435. },
  1436. },
  1437. Timestamp: start1,
  1438. },
  1439. },
  1440. expected: []metric.Update{
  1441. {
  1442. Name: metric.KubecostPVInfo,
  1443. Labels: map[string]string{
  1444. source.UIDLabel: "uuid-csi-empty",
  1445. source.PVLabel: "pv-csi-empty",
  1446. source.StorageClassLabel: "gp3",
  1447. source.ProviderIDLabel: "",
  1448. },
  1449. Value: 0,
  1450. AdditionalInfo: map[string]string{
  1451. source.UIDLabel: "uuid-csi-empty",
  1452. source.PVLabel: "pv-csi-empty",
  1453. source.StorageClassLabel: "gp3",
  1454. source.ProviderIDLabel: "",
  1455. },
  1456. },
  1457. },
  1458. },
  1459. {
  1460. // No recognized volume source: provider ID falls back to pv.Name.
  1461. name: "pv with no known volume source falls back to name",
  1462. scrapes: []scrape{
  1463. {
  1464. PVs: []*clustercache.PersistentVolume{
  1465. {
  1466. Name: "pv-nfs",
  1467. UID: "uuid-nfs",
  1468. Spec: v1.PersistentVolumeSpec{
  1469. StorageClassName: "nfs",
  1470. },
  1471. },
  1472. },
  1473. Timestamp: start1,
  1474. },
  1475. },
  1476. expected: []metric.Update{
  1477. {
  1478. Name: metric.KubecostPVInfo,
  1479. Labels: map[string]string{
  1480. source.UIDLabel: "uuid-nfs",
  1481. source.PVLabel: "pv-nfs",
  1482. source.StorageClassLabel: "nfs",
  1483. source.ProviderIDLabel: "pv-nfs",
  1484. },
  1485. Value: 0,
  1486. AdditionalInfo: map[string]string{
  1487. source.UIDLabel: "uuid-nfs",
  1488. source.PVLabel: "pv-nfs",
  1489. source.StorageClassLabel: "nfs",
  1490. source.ProviderIDLabel: "pv-nfs",
  1491. },
  1492. },
  1493. },
  1494. },
  1495. }
  1496. for _, tt := range tests {
  1497. t.Run(tt.name, func(t *testing.T) {
  1498. ks := &ClusterCacheScraper{}
  1499. var scrapeResults []metric.Update
  1500. for _, s := range tt.scrapes {
  1501. res := ks.scrapePVs(s.PVs)
  1502. scrapeResults = append(scrapeResults, res...)
  1503. }
  1504. if len(scrapeResults) != len(tt.expected) {
  1505. t.Fatalf("Expected result length of %d, got %d: %+v", len(tt.expected), len(scrapeResults), scrapeResults)
  1506. }
  1507. for i, expected := range tt.expected {
  1508. got := scrapeResults[i]
  1509. if !reflect.DeepEqual(expected, got) {
  1510. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  1511. }
  1512. // csi_volume_handle must only be present for CSI volumes with a
  1513. // non-empty handle.
  1514. if _, ok := got.Labels[source.CSIVolumeHandleLabel]; ok {
  1515. if got.Labels[source.CSIVolumeHandleLabel] == "" {
  1516. t.Errorf("index %d: csi_volume_handle label present but empty", i)
  1517. }
  1518. }
  1519. }
  1520. })
  1521. }
  1522. }
  1523. func TestToResourceUnitValue(t *testing.T) {
  1524. tests := []struct {
  1525. name string
  1526. resourceName v1.ResourceName
  1527. quantity resource.Quantity
  1528. wantResource string
  1529. wantUnit string
  1530. wantValue float64
  1531. }{
  1532. {
  1533. name: "cpu is reported in cores",
  1534. resourceName: v1.ResourceCPU,
  1535. quantity: resource.MustParse("500m"),
  1536. wantResource: "cpu",
  1537. wantUnit: "core",
  1538. wantValue: 0.5,
  1539. },
  1540. {
  1541. name: "memory is reported in bytes",
  1542. resourceName: v1.ResourceMemory,
  1543. quantity: resource.MustParse("1Ki"),
  1544. wantResource: "memory",
  1545. wantUnit: "byte",
  1546. wantValue: 1024,
  1547. },
  1548. {
  1549. name: "storage is reported in bytes",
  1550. resourceName: v1.ResourceStorage,
  1551. quantity: resource.MustParse("2Ki"),
  1552. wantResource: "storage",
  1553. wantUnit: "byte",
  1554. wantValue: 2048,
  1555. },
  1556. {
  1557. name: "ephemeral storage is reported in bytes",
  1558. resourceName: v1.ResourceEphemeralStorage,
  1559. quantity: resource.MustParse("3Ki"),
  1560. wantResource: "ephemeral-storage",
  1561. wantUnit: "byte",
  1562. wantValue: 3072,
  1563. },
  1564. {
  1565. name: "pods are reported as integers",
  1566. resourceName: v1.ResourcePods,
  1567. quantity: resource.MustParse("10"),
  1568. wantResource: "pods",
  1569. wantUnit: "integer",
  1570. wantValue: 10,
  1571. },
  1572. {
  1573. // Regression guard: the resource name is no longer sanitized, so the
  1574. // hyphen and case are preserved verbatim ("hugepages-2Mi", not
  1575. // "hugepages_2Mi").
  1576. name: "huge pages keep their raw name and are bytes",
  1577. resourceName: v1.ResourceName(v1.ResourceHugePagesPrefix + "2Mi"),
  1578. quantity: resource.MustParse("4Ki"),
  1579. wantResource: "hugepages-2Mi",
  1580. wantUnit: "byte",
  1581. wantValue: 4096,
  1582. },
  1583. {
  1584. // Regression guard: extended resource names keep '.' and '/'
  1585. // ("nvidia.com/gpu", not "nvidia_com_gpu").
  1586. name: "extended resource keeps dotted slashed name and is integer",
  1587. resourceName: v1.ResourceName("nvidia.com/gpu"),
  1588. quantity: resource.MustParse("2"),
  1589. wantResource: "nvidia.com/gpu",
  1590. wantUnit: "integer",
  1591. wantValue: 2,
  1592. },
  1593. {
  1594. name: "attachable volume resource keeps raw name and is bytes",
  1595. resourceName: v1.ResourceName(v1.ResourceAttachableVolumesPrefix + "aws-ebs"),
  1596. quantity: resource.MustParse("5"),
  1597. wantResource: "attachable-volumes-aws-ebs",
  1598. wantUnit: "byte",
  1599. wantValue: 5,
  1600. },
  1601. {
  1602. name: "unrecognized native resource returns empty",
  1603. resourceName: v1.ResourceName("kubernetes.io/somethingelse"),
  1604. quantity: resource.MustParse("1"),
  1605. wantResource: "",
  1606. wantUnit: "",
  1607. wantValue: 0,
  1608. },
  1609. }
  1610. for _, tt := range tests {
  1611. t.Run(tt.name, func(t *testing.T) {
  1612. gotResource, gotUnit, gotValue := toResourceUnitValue(tt.resourceName, tt.quantity)
  1613. if gotResource != tt.wantResource {
  1614. t.Errorf("resource = %q, want %q", gotResource, tt.wantResource)
  1615. }
  1616. if gotUnit != tt.wantUnit {
  1617. t.Errorf("unit = %q, want %q", gotUnit, tt.wantUnit)
  1618. }
  1619. if gotValue != tt.wantValue {
  1620. t.Errorf("value = %v, want %v", gotValue, tt.wantValue)
  1621. }
  1622. })
  1623. }
  1624. }
  1625. func Test_kubernetesScraper_scrapeServices(t *testing.T) {
  1626. start1, _ := time.Parse(time.RFC3339, Start1Str)
  1627. type scrape struct {
  1628. Services []*clustercache.Service
  1629. Timestamp time.Time
  1630. }
  1631. tests := []struct {
  1632. name string
  1633. nsSetup func(map[string]types.UID)
  1634. scrapes []scrape
  1635. expected []metric.Update
  1636. }{
  1637. {
  1638. name: "simple",
  1639. scrapes: []scrape{
  1640. {
  1641. Services: []*clustercache.Service{
  1642. {
  1643. Name: "service1",
  1644. Namespace: "namespace1",
  1645. UID: "uuid1",
  1646. SpecSelector: map[string]string{
  1647. "test1": "blah",
  1648. "test2": "blah2",
  1649. },
  1650. },
  1651. },
  1652. Timestamp: start1,
  1653. },
  1654. },
  1655. expected: []metric.Update{
  1656. {
  1657. Name: metric.ServiceInfo,
  1658. Labels: map[string]string{
  1659. source.UIDLabel: "uuid1",
  1660. source.ServiceLabel: "service1",
  1661. source.NamespaceLabel: "namespace1",
  1662. source.NamespaceUIDLabel: "",
  1663. source.ServiceTypeLabel: "",
  1664. source.LBIngressAddress: "",
  1665. },
  1666. Value: 0,
  1667. AdditionalInfo: map[string]string{
  1668. source.UIDLabel: "uuid1",
  1669. source.ServiceLabel: "service1",
  1670. source.NamespaceLabel: "namespace1",
  1671. source.NamespaceUIDLabel: "",
  1672. source.ServiceTypeLabel: "",
  1673. source.LBIngressAddress: "",
  1674. },
  1675. },
  1676. {
  1677. Name: metric.ServiceSelectorLabels,
  1678. Labels: map[string]string{
  1679. source.UIDLabel: "uuid1",
  1680. source.ServiceLabel: "service1",
  1681. source.NamespaceLabel: "namespace1",
  1682. source.NamespaceUIDLabel: "",
  1683. source.ServiceTypeLabel: "",
  1684. source.LBIngressAddress: "",
  1685. },
  1686. Value: 0,
  1687. AdditionalInfo: map[string]string{
  1688. "label_test1": "blah",
  1689. "label_test2": "blah2",
  1690. },
  1691. },
  1692. },
  1693. },
  1694. {
  1695. name: "with namespace index",
  1696. nsSetup: func(nsIndex map[string]types.UID) {
  1697. nsIndex["namespace1"] = "ns-uuid1"
  1698. },
  1699. scrapes: []scrape{
  1700. {
  1701. Services: []*clustercache.Service{
  1702. {
  1703. Name: "service1",
  1704. Namespace: "namespace1",
  1705. UID: "uuid1",
  1706. },
  1707. },
  1708. Timestamp: start1,
  1709. },
  1710. },
  1711. expected: []metric.Update{
  1712. {
  1713. Name: metric.ServiceInfo,
  1714. Labels: map[string]string{
  1715. source.UIDLabel: "uuid1",
  1716. source.ServiceLabel: "service1",
  1717. source.NamespaceLabel: "namespace1",
  1718. source.NamespaceUIDLabel: "ns-uuid1",
  1719. source.ServiceTypeLabel: "",
  1720. source.LBIngressAddress: "",
  1721. },
  1722. Value: 0,
  1723. AdditionalInfo: map[string]string{
  1724. source.UIDLabel: "uuid1",
  1725. source.ServiceLabel: "service1",
  1726. source.NamespaceLabel: "namespace1",
  1727. source.NamespaceUIDLabel: "ns-uuid1",
  1728. source.ServiceTypeLabel: "",
  1729. source.LBIngressAddress: "",
  1730. },
  1731. },
  1732. {
  1733. Name: metric.ServiceSelectorLabels,
  1734. Labels: map[string]string{
  1735. source.UIDLabel: "uuid1",
  1736. source.ServiceLabel: "service1",
  1737. source.NamespaceLabel: "namespace1",
  1738. source.NamespaceUIDLabel: "ns-uuid1",
  1739. source.ServiceTypeLabel: "",
  1740. source.LBIngressAddress: "",
  1741. },
  1742. Value: 0,
  1743. AdditionalInfo: map[string]string{},
  1744. },
  1745. },
  1746. },
  1747. {
  1748. name: "with LB ingress IP",
  1749. nsSetup: func(nsIndex map[string]types.UID) {
  1750. nsIndex["namespace1"] = "ns-uuid1"
  1751. },
  1752. scrapes: []scrape{
  1753. {
  1754. Services: []*clustercache.Service{
  1755. {
  1756. Name: "service1",
  1757. Namespace: "namespace1",
  1758. UID: "uuid1",
  1759. Type: v1.ServiceTypeLoadBalancer,
  1760. Status: v1.ServiceStatus{
  1761. LoadBalancer: v1.LoadBalancerStatus{
  1762. Ingress: []v1.LoadBalancerIngress{
  1763. {IP: "1.2.3.4"},
  1764. },
  1765. },
  1766. },
  1767. },
  1768. },
  1769. Timestamp: start1,
  1770. },
  1771. },
  1772. expected: []metric.Update{
  1773. {
  1774. Name: metric.ServiceInfo,
  1775. Labels: map[string]string{
  1776. source.UIDLabel: "uuid1",
  1777. source.ServiceLabel: "service1",
  1778. source.NamespaceLabel: "namespace1",
  1779. source.NamespaceUIDLabel: "ns-uuid1",
  1780. source.ServiceTypeLabel: "LoadBalancer",
  1781. source.LBIngressAddress: "1.2.3.4",
  1782. },
  1783. Value: 0,
  1784. AdditionalInfo: map[string]string{
  1785. source.UIDLabel: "uuid1",
  1786. source.ServiceLabel: "service1",
  1787. source.NamespaceLabel: "namespace1",
  1788. source.NamespaceUIDLabel: "ns-uuid1",
  1789. source.ServiceTypeLabel: "LoadBalancer",
  1790. source.LBIngressAddress: "1.2.3.4",
  1791. },
  1792. },
  1793. {
  1794. Name: metric.ServiceSelectorLabels,
  1795. Labels: map[string]string{
  1796. source.UIDLabel: "uuid1",
  1797. source.ServiceLabel: "service1",
  1798. source.NamespaceLabel: "namespace1",
  1799. source.NamespaceUIDLabel: "ns-uuid1",
  1800. source.ServiceTypeLabel: "LoadBalancer",
  1801. source.LBIngressAddress: "1.2.3.4",
  1802. },
  1803. Value: 0,
  1804. AdditionalInfo: map[string]string{},
  1805. },
  1806. },
  1807. },
  1808. }
  1809. for _, tt := range tests {
  1810. t.Run(tt.name, func(t *testing.T) {
  1811. ks := &ClusterCacheScraper{}
  1812. nsIndex := make(map[string]types.UID)
  1813. if tt.nsSetup != nil {
  1814. tt.nsSetup(nsIndex)
  1815. }
  1816. var scrapeResults []metric.Update
  1817. for _, s := range tt.scrapes {
  1818. res := ks.scrapeServices(s.Services, nsIndex)
  1819. scrapeResults = append(scrapeResults, res...)
  1820. }
  1821. if len(scrapeResults) != len(tt.expected) {
  1822. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  1823. }
  1824. for i, expected := range tt.expected {
  1825. got := scrapeResults[i]
  1826. if !reflect.DeepEqual(expected, got) {
  1827. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  1828. }
  1829. }
  1830. })
  1831. }
  1832. }
  1833. func Test_kubernetesScraper_scrapeStatefulSets(t *testing.T) {
  1834. start1, _ := time.Parse(time.RFC3339, Start1Str)
  1835. type scrape struct {
  1836. StatefulSets []*clustercache.StatefulSet
  1837. Timestamp time.Time
  1838. }
  1839. tests := []struct {
  1840. name string
  1841. nsSetup func(map[string]types.UID)
  1842. scrapes []scrape
  1843. expected []metric.Update
  1844. }{
  1845. {
  1846. name: "simple",
  1847. scrapes: []scrape{
  1848. {
  1849. StatefulSets: []*clustercache.StatefulSet{
  1850. {
  1851. Name: "statefulSet1",
  1852. Namespace: "namespace1",
  1853. UID: "uuid1",
  1854. SpecSelector: &metav1.LabelSelector{
  1855. MatchLabels: map[string]string{
  1856. "test1": "blah",
  1857. "test2": "blah2",
  1858. },
  1859. },
  1860. },
  1861. },
  1862. Timestamp: start1,
  1863. },
  1864. },
  1865. // statefulSetInfo map is shared across all 4 metrics; namespace is added
  1866. // before StatefulSetMatchLabels is appended, so all 4 reflect the final state.
  1867. expected: []metric.Update{
  1868. {
  1869. Name: metric.StatefulSetInfo,
  1870. Labels: map[string]string{
  1871. source.UIDLabel: "uuid1",
  1872. source.NamespaceUIDLabel: "",
  1873. source.StatefulSetLabel: "statefulSet1",
  1874. source.NamespaceLabel: "namespace1",
  1875. },
  1876. Value: 0,
  1877. AdditionalInfo: map[string]string{
  1878. source.UIDLabel: "uuid1",
  1879. source.NamespaceUIDLabel: "",
  1880. source.StatefulSetLabel: "statefulSet1",
  1881. source.NamespaceLabel: "namespace1",
  1882. },
  1883. },
  1884. {
  1885. Name: metric.StatefulSetLabels,
  1886. Labels: map[string]string{
  1887. source.UIDLabel: "uuid1",
  1888. source.NamespaceUIDLabel: "",
  1889. source.StatefulSetLabel: "statefulSet1",
  1890. source.NamespaceLabel: "namespace1",
  1891. },
  1892. Value: 0,
  1893. AdditionalInfo: map[string]string{},
  1894. },
  1895. {
  1896. Name: metric.StatefulSetAnnotations,
  1897. Labels: map[string]string{
  1898. source.UIDLabel: "uuid1",
  1899. source.NamespaceUIDLabel: "",
  1900. source.StatefulSetLabel: "statefulSet1",
  1901. source.NamespaceLabel: "namespace1",
  1902. },
  1903. Value: 0,
  1904. AdditionalInfo: map[string]string{},
  1905. },
  1906. {
  1907. Name: metric.StatefulSetMatchLabels,
  1908. Labels: map[string]string{
  1909. source.UIDLabel: "uuid1",
  1910. source.NamespaceUIDLabel: "",
  1911. source.StatefulSetLabel: "statefulSet1",
  1912. source.NamespaceLabel: "namespace1",
  1913. },
  1914. Value: 0,
  1915. AdditionalInfo: map[string]string{
  1916. "label_test1": "blah",
  1917. "label_test2": "blah2",
  1918. },
  1919. },
  1920. },
  1921. },
  1922. {
  1923. // statefulSetInfo map is shared; NamespaceLabel is added before MatchLabels,
  1924. // so all 4 metrics reflect the final state including namespace_uid.
  1925. name: "with namespace index",
  1926. nsSetup: func(nsIndex map[string]types.UID) {
  1927. nsIndex["namespace1"] = "ns-uuid1"
  1928. },
  1929. scrapes: []scrape{
  1930. {
  1931. StatefulSets: []*clustercache.StatefulSet{
  1932. {
  1933. Name: "statefulSet1",
  1934. Namespace: "namespace1",
  1935. UID: "uuid1",
  1936. SpecSelector: &metav1.LabelSelector{},
  1937. },
  1938. },
  1939. Timestamp: start1,
  1940. },
  1941. },
  1942. expected: []metric.Update{
  1943. {
  1944. Name: metric.StatefulSetInfo,
  1945. Labels: map[string]string{
  1946. source.UIDLabel: "uuid1",
  1947. source.NamespaceUIDLabel: "ns-uuid1",
  1948. source.StatefulSetLabel: "statefulSet1",
  1949. source.NamespaceLabel: "namespace1",
  1950. },
  1951. Value: 0,
  1952. AdditionalInfo: map[string]string{
  1953. source.UIDLabel: "uuid1",
  1954. source.NamespaceUIDLabel: "ns-uuid1",
  1955. source.StatefulSetLabel: "statefulSet1",
  1956. source.NamespaceLabel: "namespace1",
  1957. },
  1958. },
  1959. {
  1960. Name: metric.StatefulSetLabels,
  1961. Labels: map[string]string{
  1962. source.UIDLabel: "uuid1",
  1963. source.NamespaceUIDLabel: "ns-uuid1",
  1964. source.StatefulSetLabel: "statefulSet1",
  1965. source.NamespaceLabel: "namespace1",
  1966. },
  1967. Value: 0,
  1968. AdditionalInfo: map[string]string{},
  1969. },
  1970. {
  1971. Name: metric.StatefulSetAnnotations,
  1972. Labels: map[string]string{
  1973. source.UIDLabel: "uuid1",
  1974. source.NamespaceUIDLabel: "ns-uuid1",
  1975. source.StatefulSetLabel: "statefulSet1",
  1976. source.NamespaceLabel: "namespace1",
  1977. },
  1978. Value: 0,
  1979. AdditionalInfo: map[string]string{},
  1980. },
  1981. {
  1982. Name: metric.StatefulSetMatchLabels,
  1983. Labels: map[string]string{
  1984. source.UIDLabel: "uuid1",
  1985. source.NamespaceUIDLabel: "ns-uuid1",
  1986. source.StatefulSetLabel: "statefulSet1",
  1987. source.NamespaceLabel: "namespace1",
  1988. },
  1989. Value: 0,
  1990. AdditionalInfo: map[string]string{},
  1991. },
  1992. },
  1993. },
  1994. }
  1995. for _, tt := range tests {
  1996. t.Run(tt.name, func(t *testing.T) {
  1997. ks := &ClusterCacheScraper{}
  1998. nsIndex := make(map[string]types.UID, 0)
  1999. if tt.nsSetup != nil {
  2000. tt.nsSetup(nsIndex)
  2001. }
  2002. var scrapeResults []metric.Update
  2003. for _, s := range tt.scrapes {
  2004. res := ks.scrapeStatefulSets(s.StatefulSets, nsIndex)
  2005. scrapeResults = append(scrapeResults, res...)
  2006. }
  2007. if len(scrapeResults) != len(tt.expected) {
  2008. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  2009. }
  2010. for i, expected := range tt.expected {
  2011. got := scrapeResults[i]
  2012. if !reflect.DeepEqual(expected, got) {
  2013. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  2014. }
  2015. }
  2016. })
  2017. }
  2018. }
  2019. func Test_kubernetesScraper_scrapeReplicaSets(t *testing.T) {
  2020. start1, _ := time.Parse(time.RFC3339, Start1Str)
  2021. type scrape struct {
  2022. ReplicaSets []*clustercache.ReplicaSet
  2023. Timestamp time.Time
  2024. }
  2025. tests := []struct {
  2026. name string
  2027. nsSetup func(map[string]types.UID)
  2028. scrapes []scrape
  2029. expected []metric.Update
  2030. }{
  2031. {
  2032. name: "simple",
  2033. scrapes: []scrape{
  2034. {
  2035. ReplicaSets: []*clustercache.ReplicaSet{
  2036. {
  2037. Name: "replicaSet1",
  2038. Namespace: "namespace1",
  2039. UID: "uuid1",
  2040. OwnerReferences: []metav1.OwnerReference{
  2041. {
  2042. Name: "rollout1",
  2043. Kind: "Rollout",
  2044. },
  2045. },
  2046. },
  2047. {
  2048. Name: "pureReplicaSet",
  2049. Namespace: "namespace1",
  2050. UID: "uuid2",
  2051. OwnerReferences: []metav1.OwnerReference{},
  2052. },
  2053. },
  2054. Timestamp: start1,
  2055. },
  2056. },
  2057. expected: []metric.Update{
  2058. // replicaSet1: info/labels/annotations use replicaSetInfo (uid, namespace_uid, replicaset)
  2059. {
  2060. Name: metric.ReplicaSetInfo,
  2061. Labels: map[string]string{
  2062. source.UIDLabel: "uuid1",
  2063. source.NamespaceUIDLabel: "",
  2064. source.ReplicaSetLabel: "replicaSet1",
  2065. },
  2066. Value: 0,
  2067. AdditionalInfo: map[string]string{
  2068. source.UIDLabel: "uuid1",
  2069. source.NamespaceUIDLabel: "",
  2070. source.ReplicaSetLabel: "replicaSet1",
  2071. },
  2072. },
  2073. {
  2074. Name: metric.ReplicaSetLabels,
  2075. Labels: map[string]string{
  2076. source.UIDLabel: "uuid1",
  2077. source.NamespaceUIDLabel: "",
  2078. source.ReplicaSetLabel: "replicaSet1",
  2079. },
  2080. Value: 0,
  2081. AdditionalInfo: map[string]string{},
  2082. },
  2083. {
  2084. Name: metric.ReplicaSetAnnotations,
  2085. Labels: map[string]string{
  2086. source.UIDLabel: "uuid1",
  2087. source.NamespaceUIDLabel: "",
  2088. source.ReplicaSetLabel: "replicaSet1",
  2089. },
  2090. Value: 0,
  2091. AdditionalInfo: map[string]string{},
  2092. },
  2093. // replicaSet1 owner: uses replicaSetOwnerInfo (replicaset, namespace, uid) + owner fields
  2094. {
  2095. Name: metric.KubeReplicasetOwner,
  2096. Labels: map[string]string{
  2097. source.ReplicaSetLabel: "replicaSet1",
  2098. source.NamespaceLabel: "namespace1",
  2099. source.UIDLabel: "uuid1",
  2100. source.OwnerNameLabel: "rollout1",
  2101. source.OwnerKindLabel: "Rollout",
  2102. source.OwnerUIDLabel: "",
  2103. source.ControllerLabel: "false",
  2104. },
  2105. Value: 0,
  2106. AdditionalInfo: map[string]string{
  2107. source.ReplicaSetLabel: "replicaSet1",
  2108. source.NamespaceLabel: "namespace1",
  2109. source.UIDLabel: "uuid1",
  2110. source.OwnerNameLabel: "rollout1",
  2111. source.OwnerKindLabel: "Rollout",
  2112. source.OwnerUIDLabel: "",
  2113. source.ControllerLabel: "false",
  2114. },
  2115. },
  2116. // pureReplicaSet: info/labels/annotations
  2117. {
  2118. Name: metric.ReplicaSetInfo,
  2119. Labels: map[string]string{
  2120. source.UIDLabel: "uuid2",
  2121. source.NamespaceUIDLabel: "",
  2122. source.ReplicaSetLabel: "pureReplicaSet",
  2123. },
  2124. Value: 0,
  2125. AdditionalInfo: map[string]string{
  2126. source.UIDLabel: "uuid2",
  2127. source.NamespaceUIDLabel: "",
  2128. source.ReplicaSetLabel: "pureReplicaSet",
  2129. },
  2130. },
  2131. {
  2132. Name: metric.ReplicaSetLabels,
  2133. Labels: map[string]string{
  2134. source.UIDLabel: "uuid2",
  2135. source.NamespaceUIDLabel: "",
  2136. source.ReplicaSetLabel: "pureReplicaSet",
  2137. },
  2138. Value: 0,
  2139. AdditionalInfo: map[string]string{},
  2140. },
  2141. {
  2142. Name: metric.ReplicaSetAnnotations,
  2143. Labels: map[string]string{
  2144. source.UIDLabel: "uuid2",
  2145. source.NamespaceUIDLabel: "",
  2146. source.ReplicaSetLabel: "pureReplicaSet",
  2147. },
  2148. Value: 0,
  2149. AdditionalInfo: map[string]string{},
  2150. },
  2151. // pureReplicaSet owner: no owners path uses <none> sentinel, no owner_uid/controller
  2152. {
  2153. Name: metric.KubeReplicasetOwner,
  2154. Labels: map[string]string{
  2155. source.ReplicaSetLabel: "pureReplicaSet",
  2156. source.NamespaceLabel: "namespace1",
  2157. source.UIDLabel: "uuid2",
  2158. source.OwnerNameLabel: source.NoneLabelValue,
  2159. source.OwnerKindLabel: source.NoneLabelValue,
  2160. source.ControllerLabel: "false",
  2161. },
  2162. Value: 0,
  2163. AdditionalInfo: map[string]string{
  2164. source.ReplicaSetLabel: "pureReplicaSet",
  2165. source.NamespaceLabel: "namespace1",
  2166. source.UIDLabel: "uuid2",
  2167. source.OwnerNameLabel: source.NoneLabelValue,
  2168. source.OwnerKindLabel: source.NoneLabelValue,
  2169. source.ControllerLabel: "false",
  2170. },
  2171. },
  2172. },
  2173. },
  2174. {
  2175. name: "with namespace index",
  2176. nsSetup: func(nsIndex map[string]types.UID) {
  2177. nsIndex["namespace1"] = "ns-uuid1"
  2178. },
  2179. scrapes: []scrape{
  2180. {
  2181. ReplicaSets: []*clustercache.ReplicaSet{
  2182. {
  2183. Name: "replicaSet1",
  2184. Namespace: "namespace1",
  2185. UID: "uuid1",
  2186. OwnerReferences: []metav1.OwnerReference{},
  2187. },
  2188. },
  2189. Timestamp: start1,
  2190. },
  2191. },
  2192. expected: []metric.Update{
  2193. {
  2194. Name: metric.ReplicaSetInfo,
  2195. Labels: map[string]string{
  2196. source.UIDLabel: "uuid1",
  2197. source.NamespaceUIDLabel: "ns-uuid1",
  2198. source.ReplicaSetLabel: "replicaSet1",
  2199. },
  2200. Value: 0,
  2201. AdditionalInfo: map[string]string{
  2202. source.UIDLabel: "uuid1",
  2203. source.NamespaceUIDLabel: "ns-uuid1",
  2204. source.ReplicaSetLabel: "replicaSet1",
  2205. },
  2206. },
  2207. {
  2208. Name: metric.ReplicaSetLabels,
  2209. Labels: map[string]string{
  2210. source.UIDLabel: "uuid1",
  2211. source.NamespaceUIDLabel: "ns-uuid1",
  2212. source.ReplicaSetLabel: "replicaSet1",
  2213. },
  2214. Value: 0,
  2215. AdditionalInfo: map[string]string{},
  2216. },
  2217. {
  2218. Name: metric.ReplicaSetAnnotations,
  2219. Labels: map[string]string{
  2220. source.UIDLabel: "uuid1",
  2221. source.NamespaceUIDLabel: "ns-uuid1",
  2222. source.ReplicaSetLabel: "replicaSet1",
  2223. },
  2224. Value: 0,
  2225. AdditionalInfo: map[string]string{},
  2226. },
  2227. {
  2228. Name: metric.KubeReplicasetOwner,
  2229. Labels: map[string]string{
  2230. source.ReplicaSetLabel: "replicaSet1",
  2231. source.NamespaceLabel: "namespace1",
  2232. source.UIDLabel: "uuid1",
  2233. source.OwnerNameLabel: source.NoneLabelValue,
  2234. source.OwnerKindLabel: source.NoneLabelValue,
  2235. source.ControllerLabel: "false",
  2236. },
  2237. Value: 0,
  2238. AdditionalInfo: map[string]string{
  2239. source.ReplicaSetLabel: "replicaSet1",
  2240. source.NamespaceLabel: "namespace1",
  2241. source.UIDLabel: "uuid1",
  2242. source.OwnerNameLabel: source.NoneLabelValue,
  2243. source.OwnerKindLabel: source.NoneLabelValue,
  2244. source.ControllerLabel: "false",
  2245. },
  2246. },
  2247. },
  2248. },
  2249. }
  2250. for _, tt := range tests {
  2251. t.Run(tt.name, func(t *testing.T) {
  2252. ks := &ClusterCacheScraper{}
  2253. nsIndex := make(map[string]types.UID, 0)
  2254. if tt.nsSetup != nil {
  2255. tt.nsSetup(nsIndex)
  2256. }
  2257. var scrapeResults []metric.Update
  2258. for _, s := range tt.scrapes {
  2259. res := ks.scrapeReplicaSets(s.ReplicaSets, nsIndex)
  2260. scrapeResults = append(scrapeResults, res...)
  2261. }
  2262. if len(scrapeResults) != len(tt.expected) {
  2263. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  2264. }
  2265. for i, expected := range tt.expected {
  2266. got := scrapeResults[i]
  2267. if !reflect.DeepEqual(expected, got) {
  2268. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  2269. }
  2270. }
  2271. })
  2272. }
  2273. }
  2274. func Test_kubernetesScraper_scrapeResourceQuotas(t *testing.T) {
  2275. start1, _ := time.Parse(time.RFC3339, Start1Str)
  2276. type scrape struct {
  2277. ResourceQuotas []*clustercache.ResourceQuota
  2278. Timestamp time.Time
  2279. }
  2280. tests := []struct {
  2281. name string
  2282. nsSetup func(map[string]types.UID)
  2283. scrapes []scrape
  2284. expected []metric.Update
  2285. }{
  2286. {
  2287. name: "simple",
  2288. scrapes: []scrape{
  2289. {
  2290. ResourceQuotas: []*clustercache.ResourceQuota{
  2291. {
  2292. Name: "resourceQuota1",
  2293. Namespace: "namespace1",
  2294. UID: "uuid1",
  2295. Spec: v1.ResourceQuotaSpec{
  2296. Hard: v1.ResourceList{
  2297. v1.ResourceRequestsCPU: resource.MustParse("1"),
  2298. v1.ResourceRequestsMemory: resource.MustParse("1024"),
  2299. v1.ResourceLimitsCPU: resource.MustParse("2"),
  2300. v1.ResourceLimitsMemory: resource.MustParse("2048"),
  2301. },
  2302. },
  2303. Status: v1.ResourceQuotaStatus{
  2304. Used: v1.ResourceList{
  2305. v1.ResourceRequestsCPU: resource.MustParse("0.5"),
  2306. v1.ResourceRequestsMemory: resource.MustParse("512"),
  2307. v1.ResourceLimitsCPU: resource.MustParse("1"),
  2308. v1.ResourceLimitsMemory: resource.MustParse("1024"),
  2309. },
  2310. },
  2311. },
  2312. },
  2313. Timestamp: start1,
  2314. },
  2315. },
  2316. expected: []metric.Update{
  2317. {
  2318. Name: metric.ResourceQuotaInfo,
  2319. Labels: map[string]string{
  2320. source.UIDLabel: "uuid1",
  2321. source.NamespaceUIDLabel: "",
  2322. source.ResourceQuotaLabel: "resourceQuota1",
  2323. },
  2324. Value: 0,
  2325. AdditionalInfo: map[string]string{
  2326. source.UIDLabel: "uuid1",
  2327. source.NamespaceUIDLabel: "",
  2328. source.ResourceQuotaLabel: "resourceQuota1",
  2329. },
  2330. },
  2331. {
  2332. Name: metric.KubeResourceQuotaSpecResourceRequests,
  2333. Labels: map[string]string{
  2334. source.UIDLabel: "uuid1",
  2335. source.NamespaceUIDLabel: "",
  2336. source.ResourceQuotaLabel: "resourceQuota1",
  2337. source.ResourceLabel: "cpu",
  2338. source.UnitLabel: "core",
  2339. },
  2340. Value: 1,
  2341. AdditionalInfo: nil,
  2342. },
  2343. {
  2344. Name: metric.KubeResourceQuotaSpecResourceRequests,
  2345. Labels: map[string]string{
  2346. source.UIDLabel: "uuid1",
  2347. source.NamespaceUIDLabel: "",
  2348. source.ResourceQuotaLabel: "resourceQuota1",
  2349. source.ResourceLabel: "memory",
  2350. source.UnitLabel: "byte",
  2351. },
  2352. Value: 1024,
  2353. AdditionalInfo: nil,
  2354. },
  2355. {
  2356. Name: metric.KubeResourceQuotaSpecResourceLimits,
  2357. Labels: map[string]string{
  2358. source.UIDLabel: "uuid1",
  2359. source.NamespaceUIDLabel: "",
  2360. source.ResourceQuotaLabel: "resourceQuota1",
  2361. source.ResourceLabel: "cpu",
  2362. source.UnitLabel: "core",
  2363. },
  2364. Value: 2,
  2365. AdditionalInfo: nil,
  2366. },
  2367. {
  2368. Name: metric.KubeResourceQuotaSpecResourceLimits,
  2369. Labels: map[string]string{
  2370. source.UIDLabel: "uuid1",
  2371. source.NamespaceUIDLabel: "",
  2372. source.ResourceQuotaLabel: "resourceQuota1",
  2373. source.ResourceLabel: "memory",
  2374. source.UnitLabel: "byte",
  2375. },
  2376. Value: 2048,
  2377. AdditionalInfo: nil,
  2378. },
  2379. {
  2380. Name: metric.KubeResourceQuotaStatusUsedResourceRequests,
  2381. Labels: map[string]string{
  2382. source.UIDLabel: "uuid1",
  2383. source.NamespaceUIDLabel: "",
  2384. source.ResourceQuotaLabel: "resourceQuota1",
  2385. source.ResourceLabel: "cpu",
  2386. source.UnitLabel: "core",
  2387. },
  2388. Value: 0.5,
  2389. AdditionalInfo: nil,
  2390. },
  2391. {
  2392. Name: metric.KubeResourceQuotaStatusUsedResourceRequests,
  2393. Labels: map[string]string{
  2394. source.UIDLabel: "uuid1",
  2395. source.NamespaceUIDLabel: "",
  2396. source.ResourceQuotaLabel: "resourceQuota1",
  2397. source.ResourceLabel: "memory",
  2398. source.UnitLabel: "byte",
  2399. },
  2400. Value: 512,
  2401. AdditionalInfo: nil,
  2402. },
  2403. {
  2404. Name: metric.KubeResourceQuotaStatusUsedResourceLimits,
  2405. Labels: map[string]string{
  2406. source.UIDLabel: "uuid1",
  2407. source.NamespaceUIDLabel: "",
  2408. source.ResourceQuotaLabel: "resourceQuota1",
  2409. source.ResourceLabel: "cpu",
  2410. source.UnitLabel: "core",
  2411. },
  2412. Value: 1,
  2413. AdditionalInfo: nil,
  2414. },
  2415. {
  2416. Name: metric.KubeResourceQuotaStatusUsedResourceLimits,
  2417. Labels: map[string]string{
  2418. source.UIDLabel: "uuid1",
  2419. source.NamespaceUIDLabel: "",
  2420. source.ResourceQuotaLabel: "resourceQuota1",
  2421. source.ResourceLabel: "memory",
  2422. source.UnitLabel: "byte",
  2423. },
  2424. Value: 1024,
  2425. AdditionalInfo: nil,
  2426. },
  2427. },
  2428. },
  2429. {
  2430. name: "with namespace index",
  2431. nsSetup: func(nsIndex map[string]types.UID) {
  2432. nsIndex["namespace1"] = "ns-uuid1"
  2433. },
  2434. scrapes: []scrape{
  2435. {
  2436. ResourceQuotas: []*clustercache.ResourceQuota{
  2437. {
  2438. Name: "resourceQuota1",
  2439. Namespace: "namespace1",
  2440. UID: "uuid1",
  2441. Spec: v1.ResourceQuotaSpec{
  2442. Hard: v1.ResourceList{
  2443. v1.ResourceRequestsCPU: resource.MustParse("1"),
  2444. },
  2445. },
  2446. },
  2447. },
  2448. Timestamp: start1,
  2449. },
  2450. },
  2451. expected: []metric.Update{
  2452. {
  2453. Name: metric.ResourceQuotaInfo,
  2454. Labels: map[string]string{
  2455. source.UIDLabel: "uuid1",
  2456. source.NamespaceUIDLabel: "ns-uuid1",
  2457. source.ResourceQuotaLabel: "resourceQuota1",
  2458. },
  2459. Value: 0,
  2460. AdditionalInfo: map[string]string{
  2461. source.UIDLabel: "uuid1",
  2462. source.NamespaceUIDLabel: "ns-uuid1",
  2463. source.ResourceQuotaLabel: "resourceQuota1",
  2464. },
  2465. },
  2466. {
  2467. Name: metric.KubeResourceQuotaSpecResourceRequests,
  2468. Labels: map[string]string{
  2469. source.UIDLabel: "uuid1",
  2470. source.NamespaceUIDLabel: "ns-uuid1",
  2471. source.ResourceQuotaLabel: "resourceQuota1",
  2472. source.ResourceLabel: "cpu",
  2473. source.UnitLabel: "core",
  2474. },
  2475. Value: 1,
  2476. AdditionalInfo: nil,
  2477. },
  2478. },
  2479. },
  2480. }
  2481. for _, tt := range tests {
  2482. t.Run(tt.name, func(t *testing.T) {
  2483. ks := &ClusterCacheScraper{}
  2484. nsIndex := make(map[string]types.UID, 0)
  2485. if tt.nsSetup != nil {
  2486. tt.nsSetup(nsIndex)
  2487. }
  2488. var scrapeResults []metric.Update
  2489. for _, s := range tt.scrapes {
  2490. res := ks.scrapeResourceQuotas(s.ResourceQuotas, nsIndex)
  2491. scrapeResults = append(scrapeResults, res...)
  2492. }
  2493. if len(scrapeResults) != len(tt.expected) {
  2494. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  2495. }
  2496. for i, expected := range tt.expected {
  2497. got := scrapeResults[i]
  2498. if !reflect.DeepEqual(expected, got) {
  2499. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  2500. }
  2501. }
  2502. })
  2503. }
  2504. }
  2505. func Test_kubernetesScraper_scrapeDaemonSets(t *testing.T) {
  2506. start1, _ := time.Parse(time.RFC3339, Start1Str)
  2507. type scrape struct {
  2508. DaemonSets []*clustercache.DaemonSet
  2509. Timestamp time.Time
  2510. }
  2511. tests := []struct {
  2512. name string
  2513. nsSetup func(map[string]types.UID)
  2514. scrapes []scrape
  2515. expected []metric.Update
  2516. }{
  2517. {
  2518. name: "simple",
  2519. scrapes: []scrape{
  2520. {
  2521. DaemonSets: []*clustercache.DaemonSet{
  2522. {
  2523. Name: "daemonSet1",
  2524. Namespace: "namespace1",
  2525. UID: "uuid1",
  2526. },
  2527. },
  2528. Timestamp: start1,
  2529. },
  2530. },
  2531. expected: []metric.Update{
  2532. {
  2533. Name: metric.DaemonSetInfo,
  2534. Labels: map[string]string{
  2535. source.UIDLabel: "uuid1",
  2536. source.NamespaceUIDLabel: "",
  2537. source.DaemonSetLabel: "daemonSet1",
  2538. },
  2539. Value: 0,
  2540. AdditionalInfo: map[string]string{
  2541. source.UIDLabel: "uuid1",
  2542. source.NamespaceUIDLabel: "",
  2543. source.DaemonSetLabel: "daemonSet1",
  2544. },
  2545. },
  2546. {
  2547. Name: metric.DaemonSetLabels,
  2548. Labels: map[string]string{
  2549. source.UIDLabel: "uuid1",
  2550. source.NamespaceUIDLabel: "",
  2551. source.DaemonSetLabel: "daemonSet1",
  2552. },
  2553. Value: 0,
  2554. AdditionalInfo: map[string]string{},
  2555. },
  2556. {
  2557. Name: metric.DaemonSetAnnotations,
  2558. Labels: map[string]string{
  2559. source.UIDLabel: "uuid1",
  2560. source.NamespaceUIDLabel: "",
  2561. source.DaemonSetLabel: "daemonSet1",
  2562. },
  2563. Value: 0,
  2564. AdditionalInfo: map[string]string{},
  2565. },
  2566. },
  2567. },
  2568. {
  2569. name: "with namespace index",
  2570. nsSetup: func(nsIndex map[string]types.UID) {
  2571. nsIndex["namespace1"] = "ns-uuid1"
  2572. },
  2573. scrapes: []scrape{
  2574. {
  2575. DaemonSets: []*clustercache.DaemonSet{
  2576. {
  2577. Name: "daemonSet1",
  2578. Namespace: "namespace1",
  2579. UID: "uuid1",
  2580. },
  2581. },
  2582. Timestamp: start1,
  2583. },
  2584. },
  2585. expected: []metric.Update{
  2586. {
  2587. Name: metric.DaemonSetInfo,
  2588. Labels: map[string]string{
  2589. source.UIDLabel: "uuid1",
  2590. source.NamespaceUIDLabel: "ns-uuid1",
  2591. source.DaemonSetLabel: "daemonSet1",
  2592. },
  2593. Value: 0,
  2594. AdditionalInfo: map[string]string{
  2595. source.UIDLabel: "uuid1",
  2596. source.NamespaceUIDLabel: "ns-uuid1",
  2597. source.DaemonSetLabel: "daemonSet1",
  2598. },
  2599. },
  2600. {
  2601. Name: metric.DaemonSetLabels,
  2602. Labels: map[string]string{
  2603. source.UIDLabel: "uuid1",
  2604. source.NamespaceUIDLabel: "ns-uuid1",
  2605. source.DaemonSetLabel: "daemonSet1",
  2606. },
  2607. Value: 0,
  2608. AdditionalInfo: map[string]string{},
  2609. },
  2610. {
  2611. Name: metric.DaemonSetAnnotations,
  2612. Labels: map[string]string{
  2613. source.UIDLabel: "uuid1",
  2614. source.NamespaceUIDLabel: "ns-uuid1",
  2615. source.DaemonSetLabel: "daemonSet1",
  2616. },
  2617. Value: 0,
  2618. AdditionalInfo: map[string]string{},
  2619. },
  2620. },
  2621. },
  2622. {
  2623. name: "with container arguments",
  2624. scrapes: []scrape{
  2625. {
  2626. DaemonSets: []*clustercache.DaemonSet{
  2627. {
  2628. Name: "daemonSet1",
  2629. Namespace: "namespace1",
  2630. UID: "uuid1",
  2631. SpecContainers: []v1.Container{
  2632. {Args: []string{"--vgpu=2", "--bare-flag"}},
  2633. },
  2634. },
  2635. },
  2636. Timestamp: start1,
  2637. },
  2638. },
  2639. expected: []metric.Update{
  2640. {
  2641. Name: metric.DaemonSetInfo,
  2642. Labels: map[string]string{
  2643. source.UIDLabel: "uuid1",
  2644. source.NamespaceUIDLabel: "",
  2645. source.DaemonSetLabel: "daemonSet1",
  2646. },
  2647. Value: 0,
  2648. AdditionalInfo: map[string]string{
  2649. source.UIDLabel: "uuid1",
  2650. source.NamespaceUIDLabel: "",
  2651. source.DaemonSetLabel: "daemonSet1",
  2652. },
  2653. },
  2654. {
  2655. Name: metric.DaemonSetLabels,
  2656. Labels: map[string]string{
  2657. source.UIDLabel: "uuid1",
  2658. source.NamespaceUIDLabel: "",
  2659. source.DaemonSetLabel: "daemonSet1",
  2660. },
  2661. Value: 0,
  2662. AdditionalInfo: map[string]string{},
  2663. },
  2664. {
  2665. Name: metric.DaemonSetAnnotations,
  2666. Labels: map[string]string{
  2667. source.UIDLabel: "uuid1",
  2668. source.NamespaceUIDLabel: "",
  2669. source.DaemonSetLabel: "daemonSet1",
  2670. },
  2671. Value: 0,
  2672. AdditionalInfo: map[string]string{},
  2673. },
  2674. {
  2675. Name: metric.DaemonSetArguments,
  2676. Labels: map[string]string{
  2677. source.UIDLabel: "uuid1",
  2678. source.NamespaceUIDLabel: "",
  2679. source.DaemonSetLabel: "daemonSet1",
  2680. source.ArgLabel: "bare-flag",
  2681. source.ValueLabel: "",
  2682. },
  2683. Value: 0,
  2684. AdditionalInfo: map[string]string{
  2685. source.UIDLabel: "uuid1",
  2686. source.NamespaceUIDLabel: "",
  2687. source.DaemonSetLabel: "daemonSet1",
  2688. source.ArgLabel: "bare-flag",
  2689. source.ValueLabel: "",
  2690. },
  2691. },
  2692. {
  2693. Name: metric.DaemonSetArguments,
  2694. Labels: map[string]string{
  2695. source.UIDLabel: "uuid1",
  2696. source.NamespaceUIDLabel: "",
  2697. source.DaemonSetLabel: "daemonSet1",
  2698. source.ArgLabel: "vgpu",
  2699. source.ValueLabel: "2",
  2700. },
  2701. Value: 0,
  2702. AdditionalInfo: map[string]string{
  2703. source.UIDLabel: "uuid1",
  2704. source.NamespaceUIDLabel: "",
  2705. source.DaemonSetLabel: "daemonSet1",
  2706. source.ArgLabel: "vgpu",
  2707. source.ValueLabel: "2",
  2708. },
  2709. },
  2710. },
  2711. },
  2712. }
  2713. for _, tt := range tests {
  2714. t.Run(tt.name, func(t *testing.T) {
  2715. ks := &ClusterCacheScraper{}
  2716. nsIndex := make(map[string]types.UID, 0)
  2717. if tt.nsSetup != nil {
  2718. tt.nsSetup(nsIndex)
  2719. }
  2720. var scrapeResults []metric.Update
  2721. for _, s := range tt.scrapes {
  2722. res := ks.scrapeDaemonSets(s.DaemonSets, nsIndex)
  2723. scrapeResults = append(scrapeResults, res...)
  2724. }
  2725. if len(scrapeResults) != len(tt.expected) {
  2726. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  2727. }
  2728. for i, expected := range tt.expected {
  2729. got := scrapeResults[i]
  2730. if !reflect.DeepEqual(expected, got) {
  2731. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  2732. }
  2733. }
  2734. })
  2735. }
  2736. }
  2737. func Test_kubernetesScraper_scrapeJobs(t *testing.T) {
  2738. start1, _ := time.Parse(time.RFC3339, Start1Str)
  2739. type scrape struct {
  2740. Jobs []*clustercache.Job
  2741. Timestamp time.Time
  2742. }
  2743. tests := []struct {
  2744. name string
  2745. nsSetup func(map[string]types.UID)
  2746. scrapes []scrape
  2747. expected []metric.Update
  2748. }{
  2749. {
  2750. name: "simple",
  2751. scrapes: []scrape{
  2752. {
  2753. Jobs: []*clustercache.Job{
  2754. {
  2755. Name: "job1",
  2756. Namespace: "namespace1",
  2757. UID: "uuid1",
  2758. },
  2759. },
  2760. Timestamp: start1,
  2761. },
  2762. },
  2763. expected: []metric.Update{
  2764. {
  2765. Name: metric.JobInfo,
  2766. Labels: map[string]string{
  2767. source.UIDLabel: "uuid1",
  2768. source.NamespaceUIDLabel: "",
  2769. source.JobLabel: "job1",
  2770. },
  2771. Value: 0,
  2772. AdditionalInfo: map[string]string{
  2773. source.UIDLabel: "uuid1",
  2774. source.NamespaceUIDLabel: "",
  2775. source.JobLabel: "job1",
  2776. },
  2777. },
  2778. {
  2779. Name: metric.JobLabels,
  2780. Labels: map[string]string{
  2781. source.UIDLabel: "uuid1",
  2782. source.NamespaceUIDLabel: "",
  2783. source.JobLabel: "job1",
  2784. },
  2785. Value: 0,
  2786. AdditionalInfo: map[string]string{},
  2787. },
  2788. {
  2789. Name: metric.JobAnnotations,
  2790. Labels: map[string]string{
  2791. source.UIDLabel: "uuid1",
  2792. source.NamespaceUIDLabel: "",
  2793. source.JobLabel: "job1",
  2794. },
  2795. Value: 0,
  2796. AdditionalInfo: map[string]string{},
  2797. },
  2798. },
  2799. },
  2800. {
  2801. name: "with namespace index",
  2802. nsSetup: func(nsIndex map[string]types.UID) {
  2803. nsIndex["namespace1"] = "ns-uuid1"
  2804. },
  2805. scrapes: []scrape{
  2806. {
  2807. Jobs: []*clustercache.Job{
  2808. {
  2809. Name: "job1",
  2810. Namespace: "namespace1",
  2811. UID: "uuid1",
  2812. },
  2813. },
  2814. Timestamp: start1,
  2815. },
  2816. },
  2817. expected: []metric.Update{
  2818. {
  2819. Name: metric.JobInfo,
  2820. Labels: map[string]string{
  2821. source.UIDLabel: "uuid1",
  2822. source.NamespaceUIDLabel: "ns-uuid1",
  2823. source.JobLabel: "job1",
  2824. },
  2825. Value: 0,
  2826. AdditionalInfo: map[string]string{
  2827. source.UIDLabel: "uuid1",
  2828. source.NamespaceUIDLabel: "ns-uuid1",
  2829. source.JobLabel: "job1",
  2830. },
  2831. },
  2832. {
  2833. Name: metric.JobLabels,
  2834. Labels: map[string]string{
  2835. source.UIDLabel: "uuid1",
  2836. source.NamespaceUIDLabel: "ns-uuid1",
  2837. source.JobLabel: "job1",
  2838. },
  2839. Value: 0,
  2840. AdditionalInfo: map[string]string{},
  2841. },
  2842. {
  2843. Name: metric.JobAnnotations,
  2844. Labels: map[string]string{
  2845. source.UIDLabel: "uuid1",
  2846. source.NamespaceUIDLabel: "ns-uuid1",
  2847. source.JobLabel: "job1",
  2848. },
  2849. Value: 0,
  2850. AdditionalInfo: map[string]string{},
  2851. },
  2852. },
  2853. },
  2854. }
  2855. for _, tt := range tests {
  2856. t.Run(tt.name, func(t *testing.T) {
  2857. ks := &ClusterCacheScraper{}
  2858. nsIndex := make(map[string]types.UID, 0)
  2859. if tt.nsSetup != nil {
  2860. tt.nsSetup(nsIndex)
  2861. }
  2862. var scrapeResults []metric.Update
  2863. for _, s := range tt.scrapes {
  2864. res := ks.scrapeJobs(s.Jobs, nsIndex)
  2865. scrapeResults = append(scrapeResults, res...)
  2866. }
  2867. if len(scrapeResults) != len(tt.expected) {
  2868. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  2869. }
  2870. for i, expected := range tt.expected {
  2871. got := scrapeResults[i]
  2872. if !reflect.DeepEqual(expected, got) {
  2873. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  2874. }
  2875. }
  2876. })
  2877. }
  2878. }
  2879. func Test_kubernetesScraper_scrapeCronJobs(t *testing.T) {
  2880. start1, _ := time.Parse(time.RFC3339, Start1Str)
  2881. type scrape struct {
  2882. CronJobs []*clustercache.CronJob
  2883. Timestamp time.Time
  2884. }
  2885. tests := []struct {
  2886. name string
  2887. nsSetup func(map[string]types.UID)
  2888. scrapes []scrape
  2889. expected []metric.Update
  2890. }{
  2891. {
  2892. name: "simple",
  2893. scrapes: []scrape{
  2894. {
  2895. CronJobs: []*clustercache.CronJob{
  2896. {
  2897. Name: "cronJob1",
  2898. Namespace: "namespace1",
  2899. UID: "uuid1",
  2900. },
  2901. },
  2902. Timestamp: start1,
  2903. },
  2904. },
  2905. expected: []metric.Update{
  2906. {
  2907. Name: metric.CronJobInfo,
  2908. Labels: map[string]string{
  2909. source.UIDLabel: "uuid1",
  2910. source.NamespaceUIDLabel: "",
  2911. source.CronJobLabel: "cronJob1",
  2912. },
  2913. Value: 0,
  2914. AdditionalInfo: map[string]string{
  2915. source.UIDLabel: "uuid1",
  2916. source.NamespaceUIDLabel: "",
  2917. source.CronJobLabel: "cronJob1",
  2918. },
  2919. },
  2920. {
  2921. Name: metric.CronJobLabels,
  2922. Labels: map[string]string{
  2923. source.UIDLabel: "uuid1",
  2924. source.NamespaceUIDLabel: "",
  2925. source.CronJobLabel: "cronJob1",
  2926. },
  2927. Value: 0,
  2928. AdditionalInfo: map[string]string{},
  2929. },
  2930. {
  2931. Name: metric.CronJobAnnotations,
  2932. Labels: map[string]string{
  2933. source.UIDLabel: "uuid1",
  2934. source.NamespaceUIDLabel: "",
  2935. source.CronJobLabel: "cronJob1",
  2936. },
  2937. Value: 0,
  2938. AdditionalInfo: map[string]string{},
  2939. },
  2940. },
  2941. },
  2942. {
  2943. name: "with namespace index",
  2944. nsSetup: func(nsIndex map[string]types.UID) {
  2945. nsIndex["namespace1"] = "ns-uuid1"
  2946. },
  2947. scrapes: []scrape{
  2948. {
  2949. CronJobs: []*clustercache.CronJob{
  2950. {
  2951. Name: "cronJob1",
  2952. Namespace: "namespace1",
  2953. UID: "uuid1",
  2954. },
  2955. },
  2956. Timestamp: start1,
  2957. },
  2958. },
  2959. expected: []metric.Update{
  2960. {
  2961. Name: metric.CronJobInfo,
  2962. Labels: map[string]string{
  2963. source.UIDLabel: "uuid1",
  2964. source.NamespaceUIDLabel: "ns-uuid1",
  2965. source.CronJobLabel: "cronJob1",
  2966. },
  2967. Value: 0,
  2968. AdditionalInfo: map[string]string{
  2969. source.UIDLabel: "uuid1",
  2970. source.NamespaceUIDLabel: "ns-uuid1",
  2971. source.CronJobLabel: "cronJob1",
  2972. },
  2973. },
  2974. {
  2975. Name: metric.CronJobLabels,
  2976. Labels: map[string]string{
  2977. source.UIDLabel: "uuid1",
  2978. source.NamespaceUIDLabel: "ns-uuid1",
  2979. source.CronJobLabel: "cronJob1",
  2980. },
  2981. Value: 0,
  2982. AdditionalInfo: map[string]string{},
  2983. },
  2984. {
  2985. Name: metric.CronJobAnnotations,
  2986. Labels: map[string]string{
  2987. source.UIDLabel: "uuid1",
  2988. source.NamespaceUIDLabel: "ns-uuid1",
  2989. source.CronJobLabel: "cronJob1",
  2990. },
  2991. Value: 0,
  2992. AdditionalInfo: map[string]string{},
  2993. },
  2994. },
  2995. },
  2996. }
  2997. for _, tt := range tests {
  2998. t.Run(tt.name, func(t *testing.T) {
  2999. ks := &ClusterCacheScraper{}
  3000. nsIndex := make(map[string]types.UID, 0)
  3001. if tt.nsSetup != nil {
  3002. tt.nsSetup(nsIndex)
  3003. }
  3004. var scrapeResults []metric.Update
  3005. for _, s := range tt.scrapes {
  3006. res := ks.scrapeCronJobs(s.CronJobs, nsIndex)
  3007. scrapeResults = append(scrapeResults, res...)
  3008. }
  3009. if len(scrapeResults) != len(tt.expected) {
  3010. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  3011. }
  3012. for i, expected := range tt.expected {
  3013. got := scrapeResults[i]
  3014. if !reflect.DeepEqual(expected, got) {
  3015. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  3016. }
  3017. }
  3018. })
  3019. }
  3020. }