usage.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package usage
  2. import (
  3. "errors"
  4. "time"
  5. "github.com/porter-dev/porter/api/types"
  6. "github.com/porter-dev/porter/internal/kubernetes"
  7. "github.com/porter-dev/porter/internal/kubernetes/nodes"
  8. "github.com/porter-dev/porter/internal/models"
  9. "github.com/porter-dev/porter/internal/repository"
  10. "golang.org/x/oauth2"
  11. "gorm.io/gorm"
  12. )
  13. type GetUsageOpts struct {
  14. Repo repository.Repository
  15. DOConf *oauth2.Config
  16. Project *models.Project
  17. }
  18. // GetUsage gets a project's current usage and usage limit
  19. func GetUsage(opts *GetUsageOpts) (
  20. current, limit *types.ProjectUsage,
  21. resourceUse *models.ProjectUsageCache,
  22. err error,
  23. ) {
  24. limit, err = GetLimit(opts.Repo, opts.Project)
  25. if err != nil {
  26. return nil, nil, nil, err
  27. }
  28. // query for the linked cluster counts
  29. clusters, err := opts.Repo.Cluster().ListClustersByProjectID(opts.Project.ID)
  30. if err != nil {
  31. return nil, nil, nil, err
  32. }
  33. // query for the linked user counts
  34. roles, err := opts.Repo.Project().ListProjectRoles(opts.Project.ID)
  35. if err != nil {
  36. return nil, nil, nil, err
  37. }
  38. usageCache, err := opts.Repo.ProjectUsage().ReadProjectUsageCache(opts.Project.ID)
  39. isCacheFound := true
  40. if isCacheFound = !errors.Is(err, gorm.ErrRecordNotFound); err != nil && isCacheFound {
  41. return nil, nil, nil, err
  42. }
  43. // if the usage cache is 24 hours old, was not found, or usage is over limit,
  44. // re-query for the usage
  45. if !isCacheFound || usageCache.Is24HrOld() || usageCache.ResourceMemory > limit.ResourceMemory || usageCache.ResourceCPU > limit.ResourceCPU {
  46. cpu, memory, err := getResourceUsage(opts, clusters)
  47. if err != nil {
  48. return nil, nil, nil, err
  49. }
  50. if !isCacheFound {
  51. usageCache = &models.ProjectUsageCache{
  52. ProjectID: opts.Project.ID,
  53. ResourceCPU: cpu,
  54. ResourceMemory: memory,
  55. }
  56. } else {
  57. usageCache.ResourceCPU = cpu
  58. usageCache.ResourceMemory = memory
  59. }
  60. isExceeded := isUsageExceeded(usageCache, limit, uint(len(roles)), uint(len(clusters)))
  61. if !usageCache.Exceeded && isExceeded {
  62. // update the usage cache with a time exceeded
  63. currTime := time.Now()
  64. usageCache.ExceededSince = &currTime
  65. }
  66. usageCache.Exceeded = isExceeded
  67. if !isCacheFound {
  68. usageCache, err = opts.Repo.ProjectUsage().CreateProjectUsageCache(usageCache)
  69. } else {
  70. usageCache, err = opts.Repo.ProjectUsage().UpdateProjectUsageCache(usageCache)
  71. }
  72. }
  73. return &types.ProjectUsage{
  74. ResourceCPU: usageCache.ResourceCPU,
  75. ResourceMemory: usageCache.ResourceMemory,
  76. Clusters: uint(len(clusters)),
  77. Users: uint(len(roles)),
  78. }, limit, usageCache, nil
  79. }
  80. func isUsageExceeded(usageCache *models.ProjectUsageCache, limit *types.ProjectUsage, numUsers, numClusters uint) bool {
  81. isCPUExceeded := limit.ResourceCPU != 0 && usageCache.ResourceCPU > limit.ResourceCPU
  82. isMemExceeded := limit.ResourceMemory != 0 && usageCache.ResourceMemory > limit.ResourceMemory
  83. isUsersExceeded := limit.Users != 0 && numUsers > limit.Users
  84. isClustersExceeded := limit.Clusters != 0 && numClusters > limit.Clusters
  85. return isCPUExceeded || isMemExceeded || isUsersExceeded || isClustersExceeded
  86. }
  87. // gets the total resource usage across all nodes in all clusters
  88. func getResourceUsage(opts *GetUsageOpts, clusters []*models.Cluster) (uint, uint, error) {
  89. var totCPU, totMem uint = 0, 0
  90. for _, cluster := range clusters {
  91. ooc := &kubernetes.OutOfClusterConfig{
  92. Cluster: cluster,
  93. Repo: opts.Repo,
  94. DigitalOceanOAuth: opts.DOConf,
  95. }
  96. agent, err := kubernetes.GetAgentOutOfClusterConfig(ooc)
  97. if err != nil {
  98. continue
  99. // return 0, 0, fmt.Errorf("failed to get agent: %s", err.Error())
  100. }
  101. totAlloc, err := nodes.GetAllocatableResources(agent.Clientset)
  102. if err != nil {
  103. continue
  104. // return 0, 0, fmt.Errorf("failed to get alloc: %s", err.Error())
  105. }
  106. totCPU += totAlloc.CPU
  107. totMem += totAlloc.Memory
  108. }
  109. return totCPU / 1000, totMem / (1000 * 1000), nil
  110. }