feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
171
app/models/check.go
Обычный файл
171
app/models/check.go
Обычный файл
@@ -0,0 +1,171 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Check provides functionality.
|
||||
type Check struct {
|
||||
ID int64 `gorm:"primarykey" json:"id"`
|
||||
Enabled *bool `gorm:"not null;default:true" json:"enabled"`
|
||||
|
||||
MonitorID int64 `gorm:"index;type:bigint REFERENCES monitors(id)" json:"monitor_id,omitempty"`
|
||||
Monitor *Monitor `json:"-"`
|
||||
|
||||
Name *string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Interval int `json:"interval" validate:"required,gte=60"`
|
||||
|
||||
// URL to monitor
|
||||
URL *string `json:"url,omitempty"`
|
||||
|
||||
// Other settings of the check
|
||||
Settings datatypes.JSON `gorm:"not null;" json:"settings"`
|
||||
|
||||
State string `gorm:"not null;default:'UNK'" json:"state"`
|
||||
|
||||
LastStart *time.Time `json:"last_start"`
|
||||
LastEnd *time.Time `json:"last_end"`
|
||||
LastOk *time.Time `json:"last_ok"`
|
||||
LastFail *time.Time `json:"last_fail"`
|
||||
WasUp *time.Time `json:"was_up"`
|
||||
|
||||
Fails int `json:"fails"`
|
||||
Expires *time.Time `json:"expires"`
|
||||
|
||||
Error *string `json:"error"`
|
||||
Warnings pq.StringArray `gorm:"type:varchar(255)[]" json:"warnings"`
|
||||
Infos pq.StringArray `gorm:"type:varchar(255)[]" json:"infos"`
|
||||
|
||||
// RequireQuorum enables multi-region result aggregation (Phase 3 of
|
||||
// docs/todo.md): when >1 the check's State is NOT written directly by
|
||||
// ApplyRemoteCheckResult — instead CheckRegionResult rows accumulate
|
||||
// until app/models/check_aggregator.go decides OK/ERR/DEGRADED.
|
||||
// Default 1 keeps the legacy single-region behavior unchanged.
|
||||
RequireQuorum int `gorm:"not null;default:1" json:"require_quorum"`
|
||||
|
||||
// AggregationWindowSeconds is how long the aggregator waits for
|
||||
// regional CheckRegionResult rows before deciding the check's State.
|
||||
// Stored as int seconds (matching the existing GORM style — no
|
||||
// time.Duration columns) and exposed via AggregationWindow(). Default
|
||||
// 5s; ignored when RequireQuorum <= 1.
|
||||
AggregationWindowSeconds int `gorm:"not null;default:5" json:"aggregation_window_seconds"`
|
||||
|
||||
IsNew bool `gorm:"-:all" sql:"-" json:"is_new,omitempty"`
|
||||
Deleted bool `gorm:"-:all" sql:"-" json:"deleted,omitempty"`
|
||||
|
||||
Events []Event `json:"-" gorm:"many2many:event_checks;"`
|
||||
|
||||
Audited
|
||||
}
|
||||
|
||||
// ExpScope provides functionality.
|
||||
func ExpScope(q *gorm.DB) *gorm.DB {
|
||||
return q.Where("kind IN ('whois', 'ssl')").
|
||||
Preload("Monitor").
|
||||
Preload("Monitor.Group").
|
||||
Preload("Monitor.Group.Notifications").
|
||||
Preload("Monitor.Group.Notifications.Contacts").
|
||||
Where("expires < ?", time.Now().Add(time.Hour*7*24))
|
||||
}
|
||||
|
||||
// IntervalOK provides functionality.
|
||||
func (c *Check) IntervalOK() bool {
|
||||
if c.Kind == kindRKN {
|
||||
return true
|
||||
}
|
||||
|
||||
if c.Kind == kindWhois {
|
||||
return c.Interval >= 43200
|
||||
}
|
||||
|
||||
return c.Interval >= 30
|
||||
}
|
||||
|
||||
// GetLabel provides functionality.
|
||||
func (c *Check) GetLabel() string {
|
||||
if c.Name != nil {
|
||||
return *c.Name
|
||||
}
|
||||
if c.URL != nil {
|
||||
return *c.URL
|
||||
}
|
||||
return c.Kind
|
||||
}
|
||||
|
||||
// KindLabel provides functionality.
|
||||
func (c *Check) KindLabel() string {
|
||||
if c.Kind == kindWhois {
|
||||
return "регистрация домена"
|
||||
}
|
||||
if c.Kind == kindSSL {
|
||||
return "SSL сертификат"
|
||||
}
|
||||
|
||||
return c.Kind
|
||||
}
|
||||
|
||||
// GetSettings provides functionality.
|
||||
func (c *Check) GetSettings() CheckSettings {
|
||||
d := CheckSettings{}
|
||||
|
||||
err := json.Unmarshal(c.Settings, &d)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// ValidateSettings provides functionality.
|
||||
func (c *Check) ValidateSettings() error {
|
||||
if len(c.Settings) == 0 {
|
||||
c.Settings = []byte("{}")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetURL provides functionality.
|
||||
func (c *Check) GetURL() (string, error) {
|
||||
// return c.GetSettings()["url"].(string)
|
||||
if c.URL != nil {
|
||||
return *c.URL, nil
|
||||
}
|
||||
|
||||
return "http://" + c.Monitor.Host, nil
|
||||
}
|
||||
|
||||
// MetricName provides functionality.
|
||||
func (c *Check) MetricName() string {
|
||||
sanitized := strings.Map(func(r rune) rune {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == ':' {
|
||||
return r
|
||||
}
|
||||
return '_'
|
||||
}, c.Kind)
|
||||
return "c" + sanitized
|
||||
}
|
||||
|
||||
// QuorumEnabled reports whether this check should be aggregated by
|
||||
// app/models/check_aggregator.go. When false (RequireQuorum <= 1),
|
||||
// ApplyRemoteCheckResult keeps the legacy direct State update path.
|
||||
func (c *Check) QuorumEnabled() bool {
|
||||
return c.RequireQuorum > 1
|
||||
}
|
||||
|
||||
// AggregationWindow returns AggregationWindowSeconds as a time.Duration.
|
||||
// Defaults to 5s when the underlying int is zero/negative, mirroring the
|
||||
// GORM column default; callers can rely on a strictly positive value.
|
||||
func (c *Check) AggregationWindow() time.Duration {
|
||||
if c.AggregationWindowSeconds <= 0 {
|
||||
return 5 * time.Second
|
||||
}
|
||||
return time.Duration(c.AggregationWindowSeconds) * time.Second
|
||||
}
|
||||
Ссылка в новой задаче
Block a user