usage.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // +build ee
  2. package usage
  3. import (
  4. "fmt"
  5. "sync"
  6. "time"
  7. "github.com/porter-dev/porter/api/server/shared/config/env"
  8. "github.com/porter-dev/porter/api/types"
  9. "github.com/porter-dev/porter/ee/integrations/vault"
  10. "github.com/porter-dev/porter/internal/adapter"
  11. "github.com/porter-dev/porter/internal/models"
  12. "github.com/porter-dev/porter/internal/oauth"
  13. "github.com/porter-dev/porter/internal/repository"
  14. "github.com/porter-dev/porter/internal/usage"
  15. "golang.org/x/oauth2"
  16. "gorm.io/gorm"
  17. "github.com/porter-dev/porter/internal/repository/credentials"
  18. rgorm "github.com/porter-dev/porter/internal/repository/gorm"
  19. )
  20. type UsageTracker struct {
  21. db *gorm.DB
  22. repo repository.Repository
  23. doConf *oauth2.Config
  24. whitelistedUsers map[uint]uint
  25. }
  26. type UsageTrackerOpts struct {
  27. DBConf *env.DBConf
  28. DOClientID string
  29. DOClientSecret string
  30. DOScopes []string
  31. ServerURL string
  32. WhitelistedUsers map[uint]uint
  33. }
  34. const stepSize = 100
  35. func NewUsageTracker(opts *UsageTrackerOpts) (*UsageTracker, error) {
  36. db, err := adapter.New(opts.DBConf)
  37. if err != nil {
  38. return nil, err
  39. }
  40. var credBackend credentials.CredentialStorage
  41. if opts.DBConf.VaultAPIKey != "" && opts.DBConf.VaultServerURL != "" && opts.DBConf.VaultPrefix != "" {
  42. credBackend = vault.NewClient(
  43. opts.DBConf.VaultServerURL,
  44. opts.DBConf.VaultAPIKey,
  45. opts.DBConf.VaultPrefix,
  46. )
  47. }
  48. var key [32]byte
  49. for i, b := range []byte(opts.DBConf.EncryptionKey) {
  50. key[i] = b
  51. }
  52. repo := rgorm.NewRepository(db, &key, credBackend)
  53. doConf := oauth.NewDigitalOceanClient(&oauth.Config{
  54. ClientID: opts.DOClientID,
  55. ClientSecret: opts.DOClientSecret,
  56. Scopes: opts.DOScopes,
  57. BaseURL: opts.ServerURL,
  58. })
  59. return &UsageTracker{db, repo, doConf, opts.WhitelistedUsers}, nil
  60. }
  61. type UsageTrackerResponse struct {
  62. CPULimit uint
  63. CPUUsage uint
  64. MemoryLimit uint
  65. MemoryUsage uint
  66. UserLimit uint
  67. UserUsage uint
  68. ClusterLimit uint
  69. ClusterUsage uint
  70. Exceeded bool
  71. ExceededSince time.Time
  72. Project models.Project
  73. AdminEmails []string
  74. }
  75. func (u *UsageTracker) GetProjectUsage() (map[uint]*UsageTrackerResponse, error) {
  76. res := make(map[uint]*UsageTrackerResponse)
  77. // get the count of the projects
  78. var count int64
  79. if err := u.db.Model(&models.Project{}).Count(&count).Error; err != nil {
  80. return nil, err
  81. }
  82. var mu sync.Mutex
  83. var wg sync.WaitGroup
  84. worker := func(project *models.Project) {
  85. defer wg.Done()
  86. current, limit, cache, err := usage.GetUsage(&usage.GetUsageOpts{
  87. Repo: u.repo,
  88. DOConf: u.doConf,
  89. Project: project,
  90. WhitelistedUsers: u.whitelistedUsers,
  91. })
  92. if err != nil {
  93. fmt.Printf("Project %d: error getting usage: %v\n", project.ID, err)
  94. return
  95. }
  96. // get the admin emails for the project
  97. roles, err := u.repo.Project().ListProjectRoles(project.ID)
  98. if err != nil {
  99. fmt.Printf("Project %d: error getting admin emails: %v\n", project.ID, err)
  100. return
  101. }
  102. adminEmails := make([]string, 0)
  103. for _, role := range roles {
  104. if role.Kind == types.RoleAdmin {
  105. user, err := u.repo.User().ReadUser(role.UserID)
  106. if err != nil {
  107. continue
  108. }
  109. adminEmails = append(adminEmails, user.Email)
  110. }
  111. }
  112. exceededSince := cache.ExceededSince
  113. if exceededSince == nil {
  114. now := time.Now()
  115. exceededSince = &now
  116. }
  117. mu.Lock()
  118. res[project.ID] = &UsageTrackerResponse{
  119. CPUUsage: cache.ResourceCPU,
  120. CPULimit: limit.ResourceCPU,
  121. MemoryUsage: cache.ResourceMemory,
  122. MemoryLimit: limit.ResourceMemory,
  123. UserUsage: current.Users,
  124. UserLimit: limit.Users,
  125. ClusterUsage: current.Clusters,
  126. ClusterLimit: limit.Clusters,
  127. Exceeded: cache.Exceeded,
  128. ExceededSince: *exceededSince,
  129. Project: *project,
  130. AdminEmails: adminEmails,
  131. }
  132. mu.Unlock()
  133. return
  134. }
  135. // iterate (count / stepSize) + 1 times using Limit and Offset
  136. for i := 0; i < (int(count)/stepSize)+1; i++ {
  137. projects := []*models.Project{}
  138. if err := u.db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&projects).Error; err != nil {
  139. return nil, err
  140. }
  141. // go through each project
  142. for _, project := range projects {
  143. wg.Add(1)
  144. go worker(project)
  145. }
  146. wg.Wait()
  147. }
  148. return res, nil
  149. }