usage.go 4.5 KB

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