account_scheduling_threshold_reason.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package service
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "time"
  7. )
  8. const AccountSchedulingThresholdReasonSource = "account_scheduling_threshold"
  9. const (
  10. defaultTempUnschedReasonErrorMessage = "temporary scheduling block reason unavailable"
  11. defaultAccountSchedulingThresholdErrorMessage = "account scheduling threshold reached"
  12. )
  13. type tempUnschedReasonPayload struct {
  14. Source string `json:"source,omitempty"`
  15. Platform string `json:"platform,omitempty"`
  16. Window string `json:"window,omitempty"`
  17. Scope string `json:"scope,omitempty"`
  18. ThresholdPercent int `json:"threshold_percent,omitempty"`
  19. UsedPercent float64 `json:"used_percent,omitempty"`
  20. UntilUnix int64 `json:"until_unix,omitempty"`
  21. TriggeredAtUnix int64 `json:"triggered_at_unix,omitempty"`
  22. ErrorMessage string `json:"error_message"`
  23. }
  24. type AccountSchedulingThresholdReasonInput struct {
  25. Platform string
  26. Window string
  27. Scope string
  28. ThresholdPercent int
  29. UsedPercent float64
  30. Until time.Time
  31. Now time.Time
  32. }
  33. func BuildTempUnschedReasonPayload(source string, errorMessage string) string {
  34. payload := tempUnschedReasonPayload{
  35. Source: strings.TrimSpace(source),
  36. ErrorMessage: normalizeTempUnschedReasonErrorMessage(errorMessage, defaultTempUnschedReasonErrorMessage),
  37. }
  38. raw, err := json.Marshal(payload)
  39. if err != nil {
  40. return payload.ErrorMessage
  41. }
  42. return string(raw)
  43. }
  44. func BuildAccountSchedulingThresholdReason(errorMessage string) string {
  45. return BuildTempUnschedReasonPayload(
  46. AccountSchedulingThresholdReasonSource,
  47. normalizeTempUnschedReasonErrorMessage(errorMessage, defaultAccountSchedulingThresholdErrorMessage),
  48. )
  49. }
  50. func BuildDetailedAccountSchedulingThresholdReason(input AccountSchedulingThresholdReasonInput) string {
  51. triggeredAt := input.Now
  52. if triggeredAt.IsZero() {
  53. triggeredAt = time.Now().UTC()
  54. }
  55. payload := tempUnschedReasonPayload{
  56. Source: AccountSchedulingThresholdReasonSource,
  57. Platform: strings.TrimSpace(input.Platform),
  58. Window: strings.TrimSpace(input.Window),
  59. Scope: strings.TrimSpace(input.Scope),
  60. ThresholdPercent: input.ThresholdPercent,
  61. UsedPercent: input.UsedPercent,
  62. TriggeredAtUnix: triggeredAt.Unix(),
  63. ErrorMessage: buildAccountSchedulingThresholdErrorMessage(input),
  64. }
  65. if !input.Until.IsZero() {
  66. payload.UntilUnix = input.Until.UTC().Unix()
  67. }
  68. raw, err := json.Marshal(payload)
  69. if err != nil {
  70. return payload.ErrorMessage
  71. }
  72. return string(raw)
  73. }
  74. func IsAccountSchedulingThresholdReason(rawReason string) bool {
  75. payload, ok := parseTempUnschedReasonPayload(rawReason)
  76. if !ok {
  77. return false
  78. }
  79. return payload.Source == AccountSchedulingThresholdReasonSource
  80. }
  81. func parseTempUnschedReasonPayload(rawReason string) (tempUnschedReasonPayload, bool) {
  82. rawReason = strings.TrimSpace(rawReason)
  83. if rawReason == "" {
  84. return tempUnschedReasonPayload{}, false
  85. }
  86. var payload tempUnschedReasonPayload
  87. if err := json.Unmarshal([]byte(rawReason), &payload); err != nil {
  88. return tempUnschedReasonPayload{}, false
  89. }
  90. payload.Source = strings.TrimSpace(payload.Source)
  91. payload.ErrorMessage = strings.TrimSpace(payload.ErrorMessage)
  92. return payload, true
  93. }
  94. func normalizeTempUnschedReasonErrorMessage(errorMessage string, fallback string) string {
  95. errorMessage = strings.TrimSpace(errorMessage)
  96. if errorMessage != "" {
  97. return errorMessage
  98. }
  99. fallback = strings.TrimSpace(fallback)
  100. if fallback != "" {
  101. return fallback
  102. }
  103. return defaultTempUnschedReasonErrorMessage
  104. }
  105. func buildAccountSchedulingThresholdErrorMessage(input AccountSchedulingThresholdReasonInput) string {
  106. platform := strings.TrimSpace(input.Platform)
  107. if platform == "" {
  108. platform = "account"
  109. }
  110. target := strings.TrimSpace(input.Window)
  111. if scope := strings.TrimSpace(input.Scope); scope != "" {
  112. if target == "" {
  113. target = scope
  114. } else {
  115. target = target + "/" + scope
  116. }
  117. }
  118. if target == "" {
  119. target = "usage window"
  120. }
  121. threshold := input.ThresholdPercent
  122. if threshold <= 0 {
  123. threshold = 100
  124. }
  125. untilText := "the window reset"
  126. if !input.Until.IsZero() {
  127. untilText = input.Until.UTC().Format(time.RFC3339)
  128. }
  129. return fmt.Sprintf(
  130. "%s scheduling threshold reached for %s: %.1f%% used >= %d%%; paused until %s",
  131. platform,
  132. target,
  133. input.UsedPercent,
  134. threshold,
  135. untilText,
  136. )
  137. }
  138. func tempUnschedStateFromStoredReason(rawReason string, fallbackUntilUnix int64) *TempUnschedState {
  139. state := &TempUnschedState{
  140. UntilUnix: fallbackUntilUnix,
  141. RuleIndex: -1,
  142. }
  143. rawReason = strings.TrimSpace(rawReason)
  144. if rawReason == "" {
  145. state.ErrorMessage = defaultTempUnschedReasonErrorMessage
  146. return state
  147. }
  148. parsed := TempUnschedState{RuleIndex: -1}
  149. if err := json.Unmarshal([]byte(rawReason), &parsed); err == nil {
  150. if fallbackUntilUnix > parsed.UntilUnix {
  151. parsed.UntilUnix = fallbackUntilUnix
  152. }
  153. if strings.TrimSpace(parsed.ErrorMessage) == "" {
  154. if IsAccountSchedulingThresholdReason(rawReason) {
  155. parsed.ErrorMessage = defaultAccountSchedulingThresholdErrorMessage
  156. } else {
  157. parsed.ErrorMessage = rawReason
  158. }
  159. }
  160. return &parsed
  161. }
  162. state.ErrorMessage = rawReason
  163. return state
  164. }