clustercache.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. package scrape
  2. import (
  3. "fmt"
  4. "slices"
  5. "strings"
  6. "github.com/kubecost/events"
  7. "github.com/opencost/opencost/core/pkg/clustercache"
  8. "github.com/opencost/opencost/core/pkg/log"
  9. "github.com/opencost/opencost/core/pkg/source"
  10. "github.com/opencost/opencost/core/pkg/util/promutil"
  11. "github.com/opencost/opencost/modules/collector-source/pkg/event"
  12. "github.com/opencost/opencost/modules/collector-source/pkg/metric"
  13. "github.com/opencost/opencost/modules/collector-source/pkg/util"
  14. "golang.org/x/exp/maps"
  15. v1 "k8s.io/api/core/v1"
  16. "k8s.io/apimachinery/pkg/api/resource"
  17. "k8s.io/apimachinery/pkg/util/validation"
  18. )
  19. type ClusterCacheScraper struct {
  20. clusterCache clustercache.ClusterCache
  21. }
  22. func newClusterCacheScraper(clusterCache clustercache.ClusterCache) Scraper {
  23. return &ClusterCacheScraper{
  24. clusterCache: clusterCache,
  25. }
  26. }
  27. func (ccs *ClusterCacheScraper) Scrape() []metric.Update {
  28. scrapeFuncs := []ScrapeFunc{
  29. ccs.ScrapeNodes,
  30. ccs.ScrapeDeployments,
  31. ccs.ScrapeNamespaces,
  32. ccs.ScrapePods,
  33. ccs.ScrapePVCs,
  34. ccs.ScrapePVs,
  35. ccs.ScrapeServices,
  36. ccs.ScrapeStatefulSets,
  37. ccs.ScrapeReplicaSets,
  38. }
  39. return concurrentScrape(scrapeFuncs...)
  40. }
  41. func (ccs *ClusterCacheScraper) ScrapeNodes() []metric.Update {
  42. nodes := ccs.clusterCache.GetAllNodes()
  43. return ccs.scrapeNodes(nodes)
  44. }
  45. func (ccs *ClusterCacheScraper) scrapeNodes(nodes []*clustercache.Node) []metric.Update {
  46. var scrapeResults []metric.Update
  47. for _, node := range nodes {
  48. nodeInfo := map[string]string{
  49. source.NodeLabel: node.Name,
  50. source.ProviderIDLabel: node.SpecProviderID,
  51. source.UIDLabel: string(node.UID),
  52. }
  53. // Node Capacity
  54. if node.Status.Capacity != nil {
  55. if quantity, ok := node.Status.Capacity[v1.ResourceCPU]; ok {
  56. _, _, value := toResourceUnitValue(v1.ResourceCPU, quantity)
  57. scrapeResults = append(scrapeResults, metric.Update{
  58. Name: metric.KubeNodeStatusCapacityCPUCores,
  59. Labels: nodeInfo,
  60. Value: value,
  61. })
  62. }
  63. if quantity, ok := node.Status.Capacity[v1.ResourceMemory]; ok {
  64. _, _, value := toResourceUnitValue(v1.ResourceMemory, quantity)
  65. scrapeResults = append(scrapeResults, metric.Update{
  66. Name: metric.KubeNodeStatusCapacityMemoryBytes,
  67. Labels: nodeInfo,
  68. Value: value,
  69. })
  70. }
  71. }
  72. // Node Allocatable Resources
  73. if node.Status.Allocatable != nil {
  74. if quantity, ok := node.Status.Allocatable[v1.ResourceCPU]; ok {
  75. _, _, value := toResourceUnitValue(v1.ResourceCPU, quantity)
  76. scrapeResults = append(scrapeResults, metric.Update{
  77. Name: metric.KubeNodeStatusAllocatableCPUCores,
  78. Labels: nodeInfo,
  79. Value: value,
  80. })
  81. }
  82. if quantity, ok := node.Status.Allocatable[v1.ResourceMemory]; ok {
  83. _, _, value := toResourceUnitValue(v1.ResourceMemory, quantity)
  84. scrapeResults = append(scrapeResults, metric.Update{
  85. Name: metric.KubeNodeStatusAllocatableMemoryBytes,
  86. Labels: nodeInfo,
  87. Value: value,
  88. })
  89. }
  90. }
  91. // node labels
  92. labelNames, labelValues := promutil.KubeLabelsToLabels(node.Labels)
  93. nodeLabels := util.ToMap(labelNames, labelValues)
  94. scrapeResults = append(scrapeResults, metric.Update{
  95. Name: metric.KubeNodeLabels,
  96. Labels: nodeInfo,
  97. Value: 0,
  98. AdditionalInfo: nodeLabels,
  99. })
  100. }
  101. events.Dispatch(event.ScrapeEvent{
  102. ScraperName: event.KubernetesClusterScraperName,
  103. ScrapeType: event.NodeScraperType,
  104. Targets: len(nodes),
  105. Errors: nil,
  106. })
  107. return scrapeResults
  108. }
  109. func (ccs *ClusterCacheScraper) ScrapeDeployments() []metric.Update {
  110. deployments := ccs.clusterCache.GetAllDeployments()
  111. return ccs.scrapeDeployments(deployments)
  112. }
  113. func (ccs *ClusterCacheScraper) scrapeDeployments(deployments []*clustercache.Deployment) []metric.Update {
  114. var scrapeResults []metric.Update
  115. for _, deployment := range deployments {
  116. deploymentInfo := map[string]string{
  117. source.DeploymentLabel: deployment.Name,
  118. source.NamespaceLabel: deployment.Namespace,
  119. source.UIDLabel: string(deployment.UID),
  120. }
  121. // deployment labels
  122. labelNames, labelValues := promutil.KubeLabelsToLabels(deployment.MatchLabels)
  123. deploymentLabels := util.ToMap(labelNames, labelValues)
  124. scrapeResults = append(scrapeResults, metric.Update{
  125. Name: metric.DeploymentMatchLabels,
  126. Labels: deploymentInfo,
  127. Value: 0,
  128. AdditionalInfo: deploymentLabels,
  129. })
  130. }
  131. events.Dispatch(event.ScrapeEvent{
  132. ScraperName: event.KubernetesClusterScraperName,
  133. ScrapeType: event.DeploymentScraperType,
  134. Targets: len(deployments),
  135. Errors: nil,
  136. })
  137. return scrapeResults
  138. }
  139. func (ccs *ClusterCacheScraper) ScrapeNamespaces() []metric.Update {
  140. namespaces := ccs.clusterCache.GetAllNamespaces()
  141. return ccs.scrapeNamespaces(namespaces)
  142. }
  143. func (ccs *ClusterCacheScraper) scrapeNamespaces(namespaces []*clustercache.Namespace) []metric.Update {
  144. var scrapeResults []metric.Update
  145. for _, namespace := range namespaces {
  146. namespaceInfo := map[string]string{
  147. source.NamespaceLabel: namespace.Name,
  148. source.UIDLabel: string(namespace.UID),
  149. }
  150. // namespace labels
  151. labelNames, labelValues := promutil.KubeLabelsToLabels(namespace.Labels)
  152. namespaceLabels := util.ToMap(labelNames, labelValues)
  153. scrapeResults = append(scrapeResults, metric.Update{
  154. Name: metric.KubeNamespaceLabels,
  155. Labels: namespaceInfo,
  156. Value: 0,
  157. AdditionalInfo: namespaceLabels,
  158. })
  159. // namespace annotations
  160. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(namespace.Annotations)
  161. namespaceAnnotations := util.ToMap(annotationNames, annotationValues)
  162. scrapeResults = append(scrapeResults, metric.Update{
  163. Name: metric.KubeNamespaceAnnotations,
  164. Labels: namespaceInfo,
  165. Value: 0,
  166. AdditionalInfo: namespaceAnnotations,
  167. })
  168. }
  169. events.Dispatch(event.ScrapeEvent{
  170. ScraperName: event.KubernetesClusterScraperName,
  171. ScrapeType: event.NamespaceScraperType,
  172. Targets: len(namespaces),
  173. Errors: nil,
  174. })
  175. return scrapeResults
  176. }
  177. func (ccs *ClusterCacheScraper) ScrapePods() []metric.Update {
  178. pods := ccs.clusterCache.GetAllPods()
  179. return ccs.scrapePods(pods)
  180. }
  181. func (ccs *ClusterCacheScraper) scrapePods(pods []*clustercache.Pod) []metric.Update {
  182. var scrapeResults []metric.Update
  183. for _, pod := range pods {
  184. podInfo := map[string]string{
  185. source.PodLabel: pod.Name,
  186. source.NamespaceLabel: pod.Namespace,
  187. source.UIDLabel: string(pod.UID),
  188. source.NodeLabel: pod.Spec.NodeName,
  189. source.InstanceLabel: pod.Spec.NodeName,
  190. }
  191. // pod labels
  192. labelNames, labelValues := promutil.KubeLabelsToLabels(pod.Labels)
  193. podLabels := util.ToMap(labelNames, labelValues)
  194. scrapeResults = append(scrapeResults, metric.Update{
  195. Name: metric.KubePodLabels,
  196. Labels: podInfo,
  197. Value: 0,
  198. AdditionalInfo: podLabels,
  199. })
  200. // pod annotations
  201. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(pod.Annotations)
  202. podAnnotations := util.ToMap(annotationNames, annotationValues)
  203. scrapeResults = append(scrapeResults, metric.Update{
  204. Name: metric.KubePodAnnotations,
  205. Labels: podInfo,
  206. Value: 0,
  207. AdditionalInfo: podAnnotations,
  208. })
  209. // Pod owner metric
  210. for _, owner := range pod.OwnerReferences {
  211. ownerInfo := maps.Clone(podInfo)
  212. ownerInfo[source.OwnerKindLabel] = owner.Kind
  213. ownerInfo[source.OwnerNameLabel] = owner.Name
  214. scrapeResults = append(scrapeResults, metric.Update{
  215. Name: metric.KubePodOwner,
  216. Labels: ownerInfo,
  217. Value: 0,
  218. })
  219. }
  220. // Container Status
  221. for _, status := range pod.Status.ContainerStatuses {
  222. if status.State.Running != nil {
  223. containerInfo := maps.Clone(podInfo)
  224. containerInfo[source.ContainerLabel] = status.Name
  225. scrapeResults = append(scrapeResults, metric.Update{
  226. Name: metric.KubePodContainerStatusRunning,
  227. Labels: containerInfo,
  228. Value: 0,
  229. })
  230. }
  231. }
  232. for _, container := range pod.Spec.Containers {
  233. containerInfo := maps.Clone(podInfo)
  234. containerInfo[source.ContainerLabel] = container.Name
  235. // Requests
  236. if container.Resources.Requests != nil {
  237. // sorting keys here for testing purposes
  238. keys := maps.Keys(container.Resources.Requests)
  239. slices.Sort(keys)
  240. for _, resourceName := range keys {
  241. quantity := container.Resources.Requests[resourceName]
  242. resource, unit, value := toResourceUnitValue(resourceName, quantity)
  243. // failed to parse the resource type
  244. if resource == "" {
  245. log.DedupedWarningf(5, "Failed to parse resource units and quantity for resource: %s", resourceName)
  246. continue
  247. }
  248. resourceRequestInfo := maps.Clone(containerInfo)
  249. resourceRequestInfo[source.ResourceLabel] = resource
  250. resourceRequestInfo[source.UnitLabel] = unit
  251. scrapeResults = append(scrapeResults, metric.Update{
  252. Name: metric.KubePodContainerResourceRequests,
  253. Labels: resourceRequestInfo,
  254. Value: value,
  255. })
  256. }
  257. }
  258. // Limits
  259. if container.Resources.Limits != nil {
  260. // sorting keys here for testing purposes
  261. keys := maps.Keys(container.Resources.Limits)
  262. slices.Sort(keys)
  263. for _, resourceName := range keys {
  264. quantity := container.Resources.Limits[resourceName]
  265. resource, unit, value := toResourceUnitValue(resourceName, quantity)
  266. // failed to parse the resource type
  267. if resource == "" {
  268. log.DedupedWarningf(5, "Failed to parse resource units and quantity for resource: %s", resourceName)
  269. continue
  270. }
  271. resourceLimitInfo := maps.Clone(containerInfo)
  272. resourceLimitInfo[source.ResourceLabel] = resource
  273. resourceLimitInfo[source.UnitLabel] = unit
  274. scrapeResults = append(scrapeResults, metric.Update{
  275. Name: metric.KubePodContainerResourceLimits,
  276. Labels: resourceLimitInfo,
  277. Value: value,
  278. })
  279. }
  280. }
  281. }
  282. }
  283. events.Dispatch(event.ScrapeEvent{
  284. ScraperName: event.KubernetesClusterScraperName,
  285. ScrapeType: event.PodScraperType,
  286. Targets: len(pods),
  287. Errors: nil,
  288. })
  289. return scrapeResults
  290. }
  291. func (ccs *ClusterCacheScraper) ScrapePVCs() []metric.Update {
  292. pvcs := ccs.clusterCache.GetAllPersistentVolumeClaims()
  293. return ccs.scrapePVCs(pvcs)
  294. }
  295. func (ccs *ClusterCacheScraper) scrapePVCs(pvcs []*clustercache.PersistentVolumeClaim) []metric.Update {
  296. var scrapeResults []metric.Update
  297. for _, pvc := range pvcs {
  298. pvcInfo := map[string]string{
  299. source.PVCLabel: pvc.Name,
  300. source.NamespaceLabel: pvc.Namespace,
  301. source.UIDLabel: string(pvc.UID),
  302. source.VolumeNameLabel: pvc.Spec.VolumeName,
  303. source.StorageClassLabel: getPersistentVolumeClaimClass(pvc),
  304. }
  305. scrapeResults = append(scrapeResults, metric.Update{
  306. Name: metric.KubePersistentVolumeClaimInfo,
  307. Labels: pvcInfo,
  308. Value: 0,
  309. })
  310. if storage, ok := pvc.Spec.Resources.Requests[v1.ResourceStorage]; ok {
  311. scrapeResults = append(scrapeResults, metric.Update{
  312. Name: metric.KubePersistentVolumeClaimResourceRequestsStorageBytes,
  313. Labels: pvcInfo,
  314. Value: float64(storage.Value()),
  315. })
  316. }
  317. }
  318. events.Dispatch(event.ScrapeEvent{
  319. ScraperName: event.KubernetesClusterScraperName,
  320. ScrapeType: event.PvcScraperType,
  321. Targets: len(pvcs),
  322. Errors: nil,
  323. })
  324. return scrapeResults
  325. }
  326. func (ccs *ClusterCacheScraper) ScrapePVs() []metric.Update {
  327. pvs := ccs.clusterCache.GetAllPersistentVolumes()
  328. return ccs.scrapePVs(pvs)
  329. }
  330. func (ccs *ClusterCacheScraper) scrapePVs(pvs []*clustercache.PersistentVolume) []metric.Update {
  331. var scrapeResults []metric.Update
  332. for _, pv := range pvs {
  333. providerID := pv.Name
  334. // if a more accurate provider ID is available, use that
  335. if pv.Spec.CSI != nil && pv.Spec.CSI.VolumeHandle != "" {
  336. providerID = pv.Spec.CSI.VolumeHandle
  337. }
  338. pvInfo := map[string]string{
  339. source.PVLabel: pv.Name,
  340. source.UIDLabel: string(pv.UID),
  341. source.StorageClassLabel: pv.Spec.StorageClassName,
  342. source.ProviderIDLabel: providerID,
  343. }
  344. scrapeResults = append(scrapeResults, metric.Update{
  345. Name: metric.KubecostPVInfo,
  346. Labels: pvInfo,
  347. Value: 0,
  348. })
  349. if storage, ok := pv.Spec.Capacity[v1.ResourceStorage]; ok {
  350. scrapeResults = append(scrapeResults, metric.Update{
  351. Name: metric.KubePersistentVolumeCapacityBytes,
  352. Labels: pvInfo,
  353. Value: float64(storage.Value()),
  354. })
  355. }
  356. }
  357. events.Dispatch(event.ScrapeEvent{
  358. ScraperName: event.KubernetesClusterScraperName,
  359. ScrapeType: event.PvScraperType,
  360. Targets: len(pvs),
  361. Errors: nil,
  362. })
  363. return scrapeResults
  364. }
  365. func (ccs *ClusterCacheScraper) ScrapeServices() []metric.Update {
  366. services := ccs.clusterCache.GetAllServices()
  367. return ccs.scrapeServices(services)
  368. }
  369. func (ccs *ClusterCacheScraper) scrapeServices(services []*clustercache.Service) []metric.Update {
  370. var scrapeResults []metric.Update
  371. for _, service := range services {
  372. serviceInfo := map[string]string{
  373. source.ServiceLabel: service.Name,
  374. source.NamespaceLabel: service.Namespace,
  375. source.UIDLabel: string(service.UID),
  376. }
  377. // service labels
  378. labelNames, labelValues := promutil.KubeLabelsToLabels(service.SpecSelector)
  379. serviceLabels := util.ToMap(labelNames, labelValues)
  380. scrapeResults = append(scrapeResults, metric.Update{
  381. Name: metric.ServiceSelectorLabels,
  382. Labels: serviceInfo,
  383. Value: 0,
  384. AdditionalInfo: serviceLabels,
  385. })
  386. }
  387. events.Dispatch(event.ScrapeEvent{
  388. ScraperName: event.KubernetesClusterScraperName,
  389. ScrapeType: event.ServiceScraperType,
  390. Targets: len(services),
  391. Errors: nil,
  392. })
  393. return scrapeResults
  394. }
  395. func (ccs *ClusterCacheScraper) ScrapeStatefulSets() []metric.Update {
  396. statefulSets := ccs.clusterCache.GetAllStatefulSets()
  397. return ccs.scrapeStatefulSets(statefulSets)
  398. }
  399. func (ccs *ClusterCacheScraper) scrapeStatefulSets(statefulSets []*clustercache.StatefulSet) []metric.Update {
  400. var scrapeResults []metric.Update
  401. for _, statefulSet := range statefulSets {
  402. statefulSetInfo := map[string]string{
  403. source.StatefulSetLabel: statefulSet.Name,
  404. source.NamespaceLabel: statefulSet.Namespace,
  405. source.UIDLabel: string(statefulSet.UID),
  406. }
  407. // statefulSet labels
  408. labelNames, labelValues := promutil.KubeLabelsToLabels(statefulSet.SpecSelector.MatchLabels)
  409. statefulSetLabels := util.ToMap(labelNames, labelValues)
  410. scrapeResults = append(scrapeResults, metric.Update{
  411. Name: metric.StatefulSetMatchLabels,
  412. Labels: statefulSetInfo,
  413. Value: 0,
  414. AdditionalInfo: statefulSetLabels,
  415. })
  416. }
  417. events.Dispatch(event.ScrapeEvent{
  418. ScraperName: event.KubernetesClusterScraperName,
  419. ScrapeType: event.StatefulSetScraperType,
  420. Targets: len(statefulSets),
  421. Errors: nil,
  422. })
  423. return scrapeResults
  424. }
  425. func (ccs *ClusterCacheScraper) ScrapeReplicaSets() []metric.Update {
  426. replicaSets := ccs.clusterCache.GetAllReplicaSets()
  427. return ccs.scrapeReplicaSets(replicaSets)
  428. }
  429. func (ccs *ClusterCacheScraper) scrapeReplicaSets(replicaSets []*clustercache.ReplicaSet) []metric.Update {
  430. var scrapeResults []metric.Update
  431. for _, replicaSet := range replicaSets {
  432. replicaSetInfo := map[string]string{
  433. source.ReplicaSetLabel: replicaSet.Name,
  434. source.NamespaceLabel: replicaSet.Namespace,
  435. source.UIDLabel: string(replicaSet.UID),
  436. }
  437. // this specific metric exports a special <none> value for name and kind
  438. // if there are no owners
  439. if len(replicaSet.OwnerReferences) == 0 {
  440. ownerInfo := maps.Clone(replicaSetInfo)
  441. ownerInfo[source.OwnerKindLabel] = source.NoneLabelValue
  442. ownerInfo[source.OwnerNameLabel] = source.NoneLabelValue
  443. scrapeResults = append(scrapeResults, metric.Update{
  444. Name: metric.KubeReplicasetOwner,
  445. Labels: ownerInfo,
  446. Value: 0,
  447. })
  448. } else {
  449. for _, owner := range replicaSet.OwnerReferences {
  450. ownerInfo := maps.Clone(replicaSetInfo)
  451. ownerInfo[source.OwnerKindLabel] = owner.Kind
  452. ownerInfo[source.OwnerNameLabel] = owner.Name
  453. scrapeResults = append(scrapeResults, metric.Update{
  454. Name: metric.KubeReplicasetOwner,
  455. Labels: ownerInfo,
  456. Value: 0,
  457. })
  458. }
  459. }
  460. }
  461. events.Dispatch(event.ScrapeEvent{
  462. ScraperName: event.KubernetesClusterScraperName,
  463. ScrapeType: event.ReplicaSetScraperType,
  464. Targets: len(replicaSets),
  465. Errors: nil,
  466. })
  467. return scrapeResults
  468. }
  469. // getPersistentVolumeClaimClass returns StorageClassName. If no storage class was
  470. // requested, it returns "".
  471. func getPersistentVolumeClaimClass(claim *clustercache.PersistentVolumeClaim) string {
  472. // Use beta annotation first
  473. if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found {
  474. return class
  475. }
  476. if claim.Spec.StorageClassName != nil {
  477. return *claim.Spec.StorageClassName
  478. }
  479. // Special non-empty string to indicate absence of storage class.
  480. return ""
  481. }
  482. // toResourceUnitValue accepts a resource name and quantity and returns the sanitized resource, the unit, and the value in the units.
  483. // Returns an empty string for resource and unit if there was a failure.
  484. func toResourceUnitValue(resourceName v1.ResourceName, quantity resource.Quantity) (resource string, unit string, value float64) {
  485. resource = promutil.SanitizeLabelName(string(resourceName))
  486. switch resourceName {
  487. case v1.ResourceCPU:
  488. unit = "core"
  489. value = float64(quantity.MilliValue()) / 1000
  490. return
  491. case v1.ResourceStorage:
  492. fallthrough
  493. case v1.ResourceEphemeralStorage:
  494. fallthrough
  495. case v1.ResourceMemory:
  496. unit = "byte"
  497. value = float64(quantity.Value())
  498. return
  499. case v1.ResourcePods:
  500. unit = "integer"
  501. value = float64(quantity.Value())
  502. return
  503. default:
  504. if isHugePageResourceName(resourceName) || isAttachableVolumeResourceName(resourceName) {
  505. unit = "byte"
  506. value = float64(quantity.Value())
  507. return
  508. }
  509. if isExtendedResourceName(resourceName) {
  510. unit = "integer"
  511. value = float64(quantity.Value())
  512. return
  513. }
  514. }
  515. resource = ""
  516. unit = ""
  517. value = 0.0
  518. return
  519. }
  520. // isHugePageResourceName checks for a huge page container resource name
  521. func isHugePageResourceName(name v1.ResourceName) bool {
  522. return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
  523. }
  524. // isAttachableVolumeResourceName checks for attached volume container resource name
  525. func isAttachableVolumeResourceName(name v1.ResourceName) bool {
  526. return strings.HasPrefix(string(name), v1.ResourceAttachableVolumesPrefix)
  527. }
  528. // isExtendedResourceName checks for extended container resource name
  529. func isExtendedResourceName(name v1.ResourceName) bool {
  530. if isNativeResource(name) || strings.HasPrefix(string(name), v1.DefaultResourceRequestsPrefix) {
  531. return false
  532. }
  533. // Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
  534. nameForQuota := fmt.Sprintf("%s%s", v1.DefaultResourceRequestsPrefix, string(name))
  535. if errs := validation.IsQualifiedName(nameForQuota); len(errs) != 0 {
  536. return false
  537. }
  538. return true
  539. }
  540. // isNativeResource checks for a kubernetes.io/ prefixed resource name
  541. func isNativeResource(name v1.ResourceName) bool {
  542. return !strings.Contains(string(name), "/") || isPrefixedNativeResource(name)
  543. }
  544. func isPrefixedNativeResource(name v1.ResourceName) bool {
  545. return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
  546. }