usage.go 4.1 KB

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