Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
189 lines
5.2 KiB
Go
189 lines
5.2 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const AccountSchedulingThresholdReasonSource = "account_scheduling_threshold"
|
|
|
|
const (
|
|
defaultTempUnschedReasonErrorMessage = "temporary scheduling block reason unavailable"
|
|
defaultAccountSchedulingThresholdErrorMessage = "account scheduling threshold reached"
|
|
)
|
|
|
|
type tempUnschedReasonPayload struct {
|
|
Source string `json:"source,omitempty"`
|
|
Platform string `json:"platform,omitempty"`
|
|
Window string `json:"window,omitempty"`
|
|
Scope string `json:"scope,omitempty"`
|
|
ThresholdPercent int `json:"threshold_percent,omitempty"`
|
|
UsedPercent float64 `json:"used_percent,omitempty"`
|
|
UntilUnix int64 `json:"until_unix,omitempty"`
|
|
TriggeredAtUnix int64 `json:"triggered_at_unix,omitempty"`
|
|
ErrorMessage string `json:"error_message"`
|
|
}
|
|
|
|
type AccountSchedulingThresholdReasonInput struct {
|
|
Platform string
|
|
Window string
|
|
Scope string
|
|
ThresholdPercent int
|
|
UsedPercent float64
|
|
Until time.Time
|
|
Now time.Time
|
|
}
|
|
|
|
func BuildTempUnschedReasonPayload(source string, errorMessage string) string {
|
|
payload := tempUnschedReasonPayload{
|
|
Source: strings.TrimSpace(source),
|
|
ErrorMessage: normalizeTempUnschedReasonErrorMessage(errorMessage, defaultTempUnschedReasonErrorMessage),
|
|
}
|
|
|
|
raw, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return payload.ErrorMessage
|
|
}
|
|
return string(raw)
|
|
}
|
|
|
|
func BuildAccountSchedulingThresholdReason(errorMessage string) string {
|
|
return BuildTempUnschedReasonPayload(
|
|
AccountSchedulingThresholdReasonSource,
|
|
normalizeTempUnschedReasonErrorMessage(errorMessage, defaultAccountSchedulingThresholdErrorMessage),
|
|
)
|
|
}
|
|
|
|
func BuildDetailedAccountSchedulingThresholdReason(input AccountSchedulingThresholdReasonInput) string {
|
|
triggeredAt := input.Now
|
|
if triggeredAt.IsZero() {
|
|
triggeredAt = time.Now().UTC()
|
|
}
|
|
payload := tempUnschedReasonPayload{
|
|
Source: AccountSchedulingThresholdReasonSource,
|
|
Platform: strings.TrimSpace(input.Platform),
|
|
Window: strings.TrimSpace(input.Window),
|
|
Scope: strings.TrimSpace(input.Scope),
|
|
ThresholdPercent: input.ThresholdPercent,
|
|
UsedPercent: input.UsedPercent,
|
|
TriggeredAtUnix: triggeredAt.Unix(),
|
|
ErrorMessage: buildAccountSchedulingThresholdErrorMessage(input),
|
|
}
|
|
if !input.Until.IsZero() {
|
|
payload.UntilUnix = input.Until.UTC().Unix()
|
|
}
|
|
|
|
raw, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return payload.ErrorMessage
|
|
}
|
|
return string(raw)
|
|
}
|
|
|
|
func IsAccountSchedulingThresholdReason(rawReason string) bool {
|
|
payload, ok := parseTempUnschedReasonPayload(rawReason)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return payload.Source == AccountSchedulingThresholdReasonSource
|
|
}
|
|
|
|
func parseTempUnschedReasonPayload(rawReason string) (tempUnschedReasonPayload, bool) {
|
|
rawReason = strings.TrimSpace(rawReason)
|
|
if rawReason == "" {
|
|
return tempUnschedReasonPayload{}, false
|
|
}
|
|
|
|
var payload tempUnschedReasonPayload
|
|
if err := json.Unmarshal([]byte(rawReason), &payload); err != nil {
|
|
return tempUnschedReasonPayload{}, false
|
|
}
|
|
payload.Source = strings.TrimSpace(payload.Source)
|
|
payload.ErrorMessage = strings.TrimSpace(payload.ErrorMessage)
|
|
return payload, true
|
|
}
|
|
|
|
func normalizeTempUnschedReasonErrorMessage(errorMessage string, fallback string) string {
|
|
errorMessage = strings.TrimSpace(errorMessage)
|
|
if errorMessage != "" {
|
|
return errorMessage
|
|
}
|
|
|
|
fallback = strings.TrimSpace(fallback)
|
|
if fallback != "" {
|
|
return fallback
|
|
}
|
|
return defaultTempUnschedReasonErrorMessage
|
|
}
|
|
|
|
func buildAccountSchedulingThresholdErrorMessage(input AccountSchedulingThresholdReasonInput) string {
|
|
platform := strings.TrimSpace(input.Platform)
|
|
if platform == "" {
|
|
platform = "account"
|
|
}
|
|
|
|
target := strings.TrimSpace(input.Window)
|
|
if scope := strings.TrimSpace(input.Scope); scope != "" {
|
|
if target == "" {
|
|
target = scope
|
|
} else {
|
|
target = target + "/" + scope
|
|
}
|
|
}
|
|
if target == "" {
|
|
target = "usage window"
|
|
}
|
|
|
|
threshold := input.ThresholdPercent
|
|
if threshold <= 0 {
|
|
threshold = 100
|
|
}
|
|
|
|
untilText := "the window reset"
|
|
if !input.Until.IsZero() {
|
|
untilText = input.Until.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
return fmt.Sprintf(
|
|
"%s scheduling threshold reached for %s: %.1f%% used >= %d%%; paused until %s",
|
|
platform,
|
|
target,
|
|
input.UsedPercent,
|
|
threshold,
|
|
untilText,
|
|
)
|
|
}
|
|
|
|
func tempUnschedStateFromStoredReason(rawReason string, fallbackUntilUnix int64) *TempUnschedState {
|
|
state := &TempUnschedState{
|
|
UntilUnix: fallbackUntilUnix,
|
|
RuleIndex: -1,
|
|
}
|
|
|
|
rawReason = strings.TrimSpace(rawReason)
|
|
if rawReason == "" {
|
|
state.ErrorMessage = defaultTempUnschedReasonErrorMessage
|
|
return state
|
|
}
|
|
|
|
parsed := TempUnschedState{RuleIndex: -1}
|
|
if err := json.Unmarshal([]byte(rawReason), &parsed); err == nil {
|
|
if fallbackUntilUnix > parsed.UntilUnix {
|
|
parsed.UntilUnix = fallbackUntilUnix
|
|
}
|
|
if strings.TrimSpace(parsed.ErrorMessage) == "" {
|
|
if IsAccountSchedulingThresholdReason(rawReason) {
|
|
parsed.ErrorMessage = defaultAccountSchedulingThresholdErrorMessage
|
|
} else {
|
|
parsed.ErrorMessage = rawReason
|
|
}
|
|
}
|
|
return &parsed
|
|
}
|
|
|
|
state.ErrorMessage = rawReason
|
|
return state
|
|
}
|