146 строки
5.7 KiB
Go
146 строки
5.7 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gorm.io/datatypes"
|
|
"gorm.io/gorm/clause"
|
|
|
|
"rsgit.ru/rsmon/rsmon/app/models/concerns"
|
|
)
|
|
|
|
// Task kinds stored in the tasks.kind column. The plan (docs/plans/worker-notifier-mvp.md
|
|
// section 4.1) reserves the same enum for checks and notifications; this phase only
|
|
// emits notification rows but the enum is shared so the selector can stay a single
|
|
// function.
|
|
const (
|
|
TaskKindNotification = "notification"
|
|
TaskKindCheck = "check"
|
|
TaskKindServerMetric = "server_metric"
|
|
)
|
|
|
|
// Task states for the durable task envelope.
|
|
const (
|
|
TaskStateQueued = "queued"
|
|
TaskStateLeased = "leased"
|
|
TaskStateSucceeded = "succeeded"
|
|
TaskStateFailedRetry = "failed_retry"
|
|
TaskStateFailedPerm = "failed_perm"
|
|
TaskStateDead = "dead"
|
|
)
|
|
|
|
// Notification result statuses reported by the worker (mirrors the wire enum so the
|
|
// result handler can decode without re-typing the constants).
|
|
const (
|
|
NotificationResultDelivered = "delivered"
|
|
NotificationResultRetryable = "retryable"
|
|
NotificationResultPermanent = "permanent"
|
|
NotificationResultPartial = "partial"
|
|
)
|
|
|
|
// SkipLockedClause is the SELECT ... FOR UPDATE SKIP LOCKED clause used by
|
|
// every worker-pool selector (checks in check_jobs.ChecksForWorker and
|
|
// tasks in task_selector.TasksForWorker / TasksForWorkerNotification).
|
|
// Sharing the value keeps the SQL identical across selectors so goconst
|
|
// does not flag the literal, and a future change (e.g. NOWAIT) only has
|
|
// to touch one place.
|
|
var SkipLockedClause = clause.Locking{Strength: "UPDATE", Options: "SKIP LOCKED"}
|
|
|
|
// Task is the durable envelope for both check and notification work executed by the
|
|
// distributed worker pool. Selection uses FOR UPDATE SKIP LOCKED per worker poll so
|
|
// a single primary key or sequence never becomes the bottleneck.
|
|
//
|
|
// Phase 1 (this commit) only emits notification tasks. The `kind` discriminator and
|
|
// capability filters are designed to accept checks in phase 2 without a schema change.
|
|
type Task struct {
|
|
concerns.Model
|
|
|
|
JobID string `gorm:"uniqueIndex;size:64" json:"job_id"`
|
|
Kind string `gorm:"size:32;index" json:"kind"`
|
|
State string `gorm:"size:32;index" json:"state"`
|
|
LastError string `gorm:"type:text" json:"last_error"`
|
|
|
|
// Tenancy + audit anchor. AccountID is required for the capability match in
|
|
// TasksForWorker; monitor_id / message_id / contact_id are denormalized for
|
|
// fast admin queries.
|
|
AccountID int64 `gorm:"index" json:"account_id"`
|
|
MonitorID *int64 `gorm:"index" json:"monitor_id,omitempty"`
|
|
CheckID *int64 `json:"check_id,omitempty"`
|
|
MessageID *int64 `json:"message_id,omitempty"`
|
|
ContactID *int64 `json:"contact_id,omitempty"`
|
|
|
|
// Payload is the kind-specific blob the worker needs to execute. For
|
|
// notifications the producer pre-renders subject/body so the worker does not
|
|
// need templating context (see RenderNotificationContent in internal/notifier).
|
|
Payload datatypes.JSON `gorm:"type:jsonb" json:"payload"`
|
|
|
|
// Scheduling + retry envelope. NotBefore is set to NOW() by the producer and
|
|
// bumped by the result handler on retryable failures. Deadline is a soft cap
|
|
// the selector can use to skip stale tasks.
|
|
NotBefore time.Time `json:"not_before"`
|
|
Deadline *time.Time `json:"deadline,omitempty"`
|
|
|
|
// LeaseOwner + LeaseExpiresAt are owned by the selector while the task is
|
|
// in state=leased. The reaper clears them when the lease expires.
|
|
LeaseOwner string `gorm:"size:128" json:"lease_owner"`
|
|
LeaseToken string `gorm:"size:64" json:"-"`
|
|
LeaseExpiresAt *time.Time `json:"lease_expires_at,omitempty"`
|
|
|
|
Attempts int `json:"attempts"`
|
|
MaxAttempts int `json:"max_attempts"`
|
|
|
|
// IdempotencyKey is unique per logical event so a retry of the producer's
|
|
// enqueue never produces a second Task row. See EnqueueNotificationTask.
|
|
IdempotencyKey string `gorm:"uniqueIndex;size:255" json:"idempotency_key"`
|
|
|
|
// Result holds the most recent worker result body (NotificationResultReport or
|
|
// CheckResultReport shape, depending on Kind). It is JSONB so the admin UI can
|
|
// pretty-print without a separate result table for transient lookups.
|
|
Result datatypes.JSON `gorm:"type:jsonb" json:"result"`
|
|
SucceededAt *time.Time `json:"succeeded_at,omitempty"`
|
|
|
|
concerns.Timestamped
|
|
}
|
|
|
|
// TaskReplay records the single operator-initiated replay of a dead task.
|
|
// OriginalTaskID is unique, making repeated clicks/API retries idempotent.
|
|
type TaskReplay struct {
|
|
concerns.Model
|
|
OriginalTaskID int64 `gorm:"uniqueIndex" json:"original_task_id"`
|
|
RequeuedTaskID int64 `gorm:"uniqueIndex" json:"requeued_task_id"`
|
|
OperatorUserID int64 `gorm:"index" json:"operator_user_id"`
|
|
concerns.Timestamped
|
|
}
|
|
|
|
// TableName overrides the default table name so pluralization stays consistent
|
|
// with the rest of the schema (tasks, not "task" or "taskses").
|
|
func (Task) TableName() string {
|
|
return "tasks"
|
|
}
|
|
|
|
// NotificationDelivery is the per-attempt audit row required by section 7.5 of the
|
|
// plan ("Audit rows: each successful or failed delivery writes a row in a new
|
|
// notification_deliveries table"). The result handler appends one row per result
|
|
// frame, which lets support answer "did the customer ever get this alert" without
|
|
// scanning application logs.
|
|
type NotificationDelivery struct {
|
|
concerns.Model
|
|
|
|
MessageID int64 `gorm:"index" json:"message_id"`
|
|
WorkerID string `gorm:"size:128;index" json:"worker_id"`
|
|
TaskID int64 `gorm:"index" json:"task_id"`
|
|
|
|
Status string `gorm:"size:32" json:"status"`
|
|
Error string `gorm:"type:text" json:"error"`
|
|
|
|
DurationMs int `json:"duration_ms"`
|
|
ProviderResponse string `gorm:"type:text" json:"provider_response"`
|
|
|
|
concerns.Timestamped
|
|
}
|
|
|
|
// TableName mirrors the plan's preferred lowercase plural.
|
|
func (NotificationDelivery) TableName() string {
|
|
return "notification_deliveries"
|
|
}
|