2
0

usage.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. package middleware
  2. import (
  3. "net/http"
  4. "github.com/porter-dev/porter/internal/telemetry"
  5. "github.com/porter-dev/porter/api/server/shared/apierrors"
  6. "github.com/porter-dev/porter/api/server/shared/config"
  7. "github.com/porter-dev/porter/api/types"
  8. "github.com/porter-dev/porter/internal/models"
  9. "github.com/porter-dev/porter/internal/usage"
  10. )
  11. type UsageMiddleware struct {
  12. config *config.Config
  13. metric types.UsageMetric
  14. }
  15. func NewUsageMiddleware(config *config.Config, metric types.UsageMetric) *UsageMiddleware {
  16. return &UsageMiddleware{config, metric}
  17. }
  18. var UsageErrFmt = "usage limit reached for metric %s: limit %d, requested %d"
  19. func (b *UsageMiddleware) Middleware(next http.Handler) http.Handler {
  20. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  21. ctx, span := telemetry.NewSpan(r.Context(), "middleware-usage")
  22. defer span.End()
  23. proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
  24. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "project-id", Value: proj.ID})
  25. // get the project usage limits
  26. currentUsage, limit, _, err := usage.GetUsage(&usage.GetUsageOpts{
  27. Project: proj,
  28. DOConf: b.config.DOConf,
  29. Repo: b.config.Repo,
  30. WhitelistedUsers: b.config.WhitelistedUsers,
  31. ClusterControlPlaneServiceClient: b.config.ClusterControlPlaneClient,
  32. })
  33. if err != nil {
  34. err = telemetry.Error(ctx, span, err, "error getting usage")
  35. apierrors.HandleAPIError(
  36. b.config.Logger,
  37. b.config.Alerter,
  38. w, r,
  39. apierrors.NewErrInternal(err),
  40. true,
  41. )
  42. return
  43. }
  44. telemetry.WithAttributes(span,
  45. telemetry.AttributeKV{Key: "users-current-usage", Value: currentUsage.Users},
  46. telemetry.AttributeKV{Key: "users-limit", Value: limit.Users},
  47. telemetry.AttributeKV{Key: "cpu-current-usage", Value: currentUsage.ResourceCPU},
  48. telemetry.AttributeKV{Key: "cpu-limit", Value: limit.ResourceCPU},
  49. telemetry.AttributeKV{Key: "memory-current-usage", Value: currentUsage.ResourceMemory},
  50. telemetry.AttributeKV{Key: "memory-limit", Value: limit.ResourceMemory},
  51. telemetry.AttributeKV{Key: "clusters-current-usage", Value: currentUsage.Clusters},
  52. telemetry.AttributeKV{Key: "clusters-limit", Value: limit.Clusters},
  53. )
  54. // check the usage limits
  55. allowed := allowUsage(limit, currentUsage, b.metric)
  56. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "allowed", Value: allowed})
  57. r = r.Clone(ctx)
  58. next.ServeHTTP(w, r)
  59. })
  60. }
  61. // checkUsage returns true if the increase in usage is allowed for the given metric,
  62. // false otherwise. We only assume increments of 1 in usage for now.
  63. func allowUsage(
  64. plan, current *types.ProjectUsage,
  65. metric types.UsageMetric,
  66. ) bool {
  67. switch metric {
  68. case types.Users:
  69. return plan.Users == 0 || plan.Users >= current.Users+1
  70. case types.Clusters:
  71. return plan.Clusters == 0 || plan.Clusters >= current.Clusters+1
  72. default:
  73. return false
  74. }
  75. }
  76. func getMetricUsage(
  77. plan, current *types.ProjectUsage,
  78. metric types.UsageMetric,
  79. ) (limit uint, curr uint) {
  80. switch metric {
  81. case types.CPU:
  82. return plan.ResourceCPU, current.ResourceCPU
  83. case types.Memory:
  84. return plan.ResourceMemory, current.ResourceMemory
  85. case types.Users:
  86. return plan.Users, current.Users
  87. case types.Clusters:
  88. return plan.Clusters, current.Clusters
  89. default:
  90. return 0, 0
  91. }
  92. }