usage.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  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. }
  19. // GetUsage gets a project's current usage and usage limit
  20. func GetUsage(opts *GetUsageOpts) (
  21. current, limit *types.ProjectUsage,
  22. resourceUse *models.ProjectUsageCache,
  23. err error,
  24. ) {
  25. limit, err = GetLimit(opts.Repo, opts.Project)
  26. if err != nil {
  27. return nil, nil, nil, err
  28. }
  29. // query for the linked cluster counts
  30. clusters, err := opts.Repo.Cluster().ListClustersByProjectID(opts.Project.ID)
  31. if err != nil {
  32. return nil, nil, nil, err
  33. }
  34. // query for the linked user counts
  35. roles, err := opts.Repo.Project().ListProjectRoles(opts.Project.ID)
  36. if err != nil {
  37. return nil, nil, nil, err
  38. }
  39. usageCache, err := opts.Repo.ProjectUsage().ReadProjectUsageCache(opts.Project.ID)
  40. isCacheFound := true
  41. if isCacheFound = !errors.Is(err, gorm.ErrRecordNotFound); err != nil && isCacheFound {
  42. return nil, nil, nil, err
  43. }
  44. // if the usage cache is 24 hours old, was not found, or usage is over limit,
  45. // re-query for the usage
  46. if !isCacheFound || usageCache.Is24HrOld() || usageCache.ResourceMemory > limit.ResourceMemory || usageCache.ResourceCPU > limit.ResourceCPU {
  47. cpu, memory, err := getResourceUsage(opts, clusters)
  48. if err != nil {
  49. return nil, nil, nil, err
  50. }
  51. if !isCacheFound {
  52. usageCache = &models.ProjectUsageCache{
  53. ProjectID: opts.Project.ID,
  54. ResourceCPU: cpu,
  55. ResourceMemory: memory,
  56. }
  57. } else {
  58. usageCache.ResourceCPU = cpu
  59. usageCache.ResourceMemory = memory
  60. }
  61. isExceeded := usageCache.ResourceCPU > limit.ResourceCPU || usageCache.ResourceMemory > limit.ResourceMemory
  62. if !usageCache.Exceeded && isExceeded {
  63. // update the usage cache with a time exceeded
  64. currTime := time.Now()
  65. usageCache.ExceededSince = &currTime
  66. }
  67. usageCache.Exceeded = isExceeded
  68. if !isCacheFound {
  69. usageCache, err = opts.Repo.ProjectUsage().CreateProjectUsageCache(usageCache)
  70. } else {
  71. usageCache, err = opts.Repo.ProjectUsage().UpdateProjectUsageCache(usageCache)
  72. }
  73. }
  74. return &types.ProjectUsage{
  75. ResourceCPU: usageCache.ResourceCPU,
  76. ResourceMemory: usageCache.ResourceMemory,
  77. Clusters: uint(len(clusters)),
  78. Users: uint(len(roles)),
  79. }, limit, usageCache, nil
  80. }
  81. // gets the total resource usage across all nodes in all clusters
  82. func getResourceUsage(opts *GetUsageOpts, clusters []*models.Cluster) (uint, uint, error) {
  83. var totCPU, totMem uint = 0, 0
  84. for _, cluster := range clusters {
  85. ooc := &kubernetes.OutOfClusterConfig{
  86. Cluster: cluster,
  87. Repo: opts.Repo,
  88. DigitalOceanOAuth: opts.DOConf,
  89. }
  90. agent, err := kubernetes.GetAgentOutOfClusterConfig(ooc)
  91. if err != nil {
  92. return 0, 0, fmt.Errorf("failed to get agent: %s", err.Error())
  93. }
  94. totAlloc, err := nodes.GetAllocatableResources(agent.Clientset)
  95. if err != nil {
  96. return 0, 0, fmt.Errorf("failed to get alloc: %s", err.Error())
  97. }
  98. totCPU += totAlloc.CPU
  99. totMem += totAlloc.Memory
  100. }
  101. return totCPU / 1000, totMem / (1024 * 1024), nil
  102. }