171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.ostiwe.com/ostiwe-com/status/model"
|
|
"git.ostiwe.com/ostiwe-com/status/modules/log"
|
|
"git.ostiwe.com/ostiwe-com/status/modules/scheduler"
|
|
"git.ostiwe.com/ostiwe-com/status/repository"
|
|
"github.com/go-co-op/gocron/v2"
|
|
"github.com/samber/lo"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
httpObserveFailed = errors.New("http observe fail")
|
|
httpObserveMaxTries = errors.New("http observe max tries")
|
|
|
|
GlobalCheckService Check
|
|
)
|
|
|
|
func init() {
|
|
log.Global.Put(log.CRON, logrus.New())
|
|
log.Global.Put(log.Observer, logrus.New())
|
|
}
|
|
|
|
type Check interface {
|
|
Observe(ctx context.Context, srv *model.Service) error
|
|
RegisterStatus(ctx context.Context, serviceID uint64, status model.StatusCode, meta ResponseMeta) error
|
|
RegisterTasks(ctx context.Context) error
|
|
}
|
|
|
|
type check struct {
|
|
serviceRepository repository.Service
|
|
statusRepository repository.Status
|
|
appScheduler scheduler.AppScheduler
|
|
}
|
|
|
|
func NewCheck(appScheduler scheduler.AppScheduler) Check {
|
|
return &check{
|
|
serviceRepository: repository.NewServiceRepository(),
|
|
statusRepository: repository.NewStatusRepository(),
|
|
appScheduler: appScheduler,
|
|
}
|
|
}
|
|
|
|
func (c *check) RegisterTasks(ctx context.Context) error {
|
|
services, err := c.serviceRepository.All(ctx, -1, 0, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, service := range services {
|
|
job, jobCreateErr := c.appScheduler.NewServiceJob(
|
|
service.ID,
|
|
gocron.DurationJob(time.Duration(service.Config.Interval)*time.Second),
|
|
gocron.NewTask(c.Observe, ctx, &service),
|
|
gocron.WithName(fmt.Sprintf("task-service-%d", service.ID)),
|
|
)
|
|
if jobCreateErr != nil {
|
|
return jobCreateErr
|
|
}
|
|
|
|
log.Global.Get(log.CRON).Infof(
|
|
"Registered service observe task for service with id - %d; task id - UUID: %s; Job interval: %d(s)",
|
|
service.ID,
|
|
job.ID().String(),
|
|
service.Config.Interval,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *check) Observe(ctx context.Context, srv *model.Service) error {
|
|
// If the observation of an HTTP service failed, check the last N statuses.
|
|
// If the first one has a failure status, set it as failed.
|
|
// If all statuses in the last N are warning, set it as failed.
|
|
// Otherwise, set the status as warning.
|
|
if srv.Type == "http" {
|
|
meta, err := c.ObserveHttp(ctx, srv)
|
|
if err == nil {
|
|
return c.RegisterStatus(ctx, srv.ID, model.StatusOK, *meta)
|
|
}
|
|
|
|
lastNStatuses, err := c.statusRepository.LastNStatuses(ctx, srv.ID, uint(srv.Config.MaxFails))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(lastNStatuses) > 0 && lastNStatuses[0].Status == model.StatusFailed {
|
|
return c.RegisterStatus(ctx, srv.ID, model.StatusFailed, *meta)
|
|
}
|
|
|
|
everyWarn := lo.EveryBy(lastNStatuses, func(item model.Status) bool {
|
|
return item.Status == model.StatusWarn
|
|
})
|
|
|
|
if everyWarn {
|
|
return c.RegisterStatus(ctx, srv.ID, model.StatusFailed, *meta)
|
|
}
|
|
|
|
return c.RegisterStatus(ctx, srv.ID, model.StatusWarn, *meta)
|
|
}
|
|
|
|
// Todo: return err
|
|
return nil
|
|
}
|
|
|
|
func (c *check) RegisterStatus(ctx context.Context, serviceID uint64, status model.StatusCode, meta ResponseMeta) error {
|
|
return c.statusRepository.Add(ctx, model.Status{
|
|
ServiceID: serviceID,
|
|
Status: status,
|
|
ResponseTime: meta.ResponseTime,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
|
|
type ResponseMeta struct {
|
|
ResponseTime uint64
|
|
}
|
|
|
|
func (c *check) ObserveHttp(ctx context.Context, service *model.Service) (*ResponseMeta, error) {
|
|
var meta = &ResponseMeta{
|
|
ResponseTime: 0,
|
|
}
|
|
|
|
var startTime = time.Now()
|
|
|
|
defer func() {
|
|
meta.ResponseTime = uint64(time.Since(startTime).Milliseconds())
|
|
}()
|
|
|
|
if service.Config == nil || service.Config.HTTPConfig == nil {
|
|
return meta, errors.New("service has no config for http")
|
|
}
|
|
|
|
logger := log.Global.Get(log.Observer)
|
|
logger.Debugf("Start observe service - %d", service.ID)
|
|
|
|
conf := service.Config.HTTPConfig
|
|
|
|
client := &http.Client{
|
|
Timeout: time.Duration(service.Config.Timeout) * time.Second,
|
|
}
|
|
|
|
request, err := http.NewRequestWithContext(ctx, conf.Method, service.Host, nil)
|
|
if err != nil {
|
|
return meta, err
|
|
}
|
|
|
|
for s := range conf.Headers {
|
|
request.Header.Set(s, conf.Headers[s])
|
|
}
|
|
|
|
response, err := client.Do(request)
|
|
if err != nil {
|
|
return meta, err
|
|
}
|
|
|
|
if response.StatusCode != http.StatusOK {
|
|
return meta, httpObserveFailed
|
|
}
|
|
|
|
return meta, nil
|
|
}
|