feat: Add check service (WIP)
This commit is contained in:
5
main.go
5
main.go
@@ -3,11 +3,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.ostiwe.com/ostiwe-com/status/model"
|
"git.ostiwe.com/ostiwe-com/status/model"
|
||||||
"git.ostiwe.com/ostiwe-com/status/modules/db"
|
"git.ostiwe.com/ostiwe-com/status/modules/db"
|
||||||
appLog "git.ostiwe.com/ostiwe-com/status/modules/log"
|
appLog "git.ostiwe.com/ostiwe-com/status/modules/log"
|
||||||
"git.ostiwe.com/ostiwe-com/status/router"
|
"git.ostiwe.com/ostiwe-com/status/router"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/service"
|
||||||
"git.ostiwe.com/ostiwe-com/status/version"
|
"git.ostiwe.com/ostiwe-com/status/version"
|
||||||
"github.com/alexflint/go-arg"
|
"github.com/alexflint/go-arg"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@@ -54,6 +56,9 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Start service observer")
|
||||||
|
go service.NewCheck(20).Start(time.Second * 10)
|
||||||
|
|
||||||
appLog.Global.Put(appLog.SERVER, logrus.New())
|
appLog.Global.Put(appLog.SERVER, logrus.New())
|
||||||
appLog.Global.Get(appLog.SERVER).Info("Startup server on port: ", args.Server.Port)
|
appLog.Global.Get(appLog.SERVER).Info("Startup server on port: ", args.Server.Port)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
Authorization string `json:"authorization"`
|
Method string `json:"method"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceTypeCheckConfig struct {
|
type ServiceTypeCheckConfig struct {
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusOK = "ok" // Means - response ok, service is alive
|
||||||
|
StatusFailed = "failed" // Means - response failed, all tries failed, service down
|
||||||
|
StatusWarn = "warn" // Means - response failed after N tries and still watched
|
||||||
|
)
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
ID int `gorm:"primary_key;auto_increment" json:"-"`
|
ID int `gorm:"primary_key;auto_increment" json:"-"`
|
||||||
ServiceID int `gorm:"one" json:"-"`
|
ServiceID int `gorm:"one" json:"-"`
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const (
|
|||||||
SERVER = "server"
|
SERVER = "server"
|
||||||
SYSTEM = "system"
|
SYSTEM = "system"
|
||||||
DATABASE = "database"
|
DATABASE = "database"
|
||||||
|
CRON = "cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Global *LoggerManager
|
var Global *LoggerManager
|
||||||
|
|||||||
5
pkg/slice/chunk.go
Normal file
5
pkg/slice/chunk.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package slice
|
||||||
|
|
||||||
|
func ChunkSlice[T any](slice []T, chunkSize int) [][]T {
|
||||||
|
|
||||||
|
}
|
||||||
24
repository/status.go
Normal file
24
repository/status.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/model"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/modules/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status interface {
|
||||||
|
Add(status model.Status) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type status struct {
|
||||||
|
repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatusRepository() Status {
|
||||||
|
return &status{
|
||||||
|
repository: repository{db: db.Global},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s status) Add(status model.Status) error {
|
||||||
|
return s.db.Create(&status).Error
|
||||||
|
}
|
||||||
175
service/check.go
Normal file
175
service/check.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/model"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/modules/log"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/repository"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpObserveFailed = errors.New("http observe fail")
|
||||||
|
httpObserveMaxTries = errors.New("http observe max tries")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Global.Put(log.CRON, logrus.New())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Check interface {
|
||||||
|
Start(interval time.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
type check struct {
|
||||||
|
serviceRepository repository.Service
|
||||||
|
maxServicesPerRoutine int
|
||||||
|
statusRepository repository.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheck(maxServicesPerRoutine int) Check {
|
||||||
|
if maxServicesPerRoutine <= 0 {
|
||||||
|
maxServicesPerRoutine = 15
|
||||||
|
}
|
||||||
|
|
||||||
|
return &check{
|
||||||
|
maxServicesPerRoutine: maxServicesPerRoutine,
|
||||||
|
serviceRepository: repository.NewServiceRepository(),
|
||||||
|
statusRepository: repository.NewStatusRepository(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c check) Start(interval time.Duration) {
|
||||||
|
for {
|
||||||
|
time.Sleep(interval)
|
||||||
|
|
||||||
|
services, err := c.serviceRepository.All(context.Background(), -1, 0, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Global.Get(log.CRON).Error(err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
|
||||||
|
chunked := slices.Chunk(services, c.maxServicesPerRoutine)
|
||||||
|
|
||||||
|
for i := range chunked {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go c.observeChunk(wg, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c check) observeChunk(wg *sync.WaitGroup, chunk []model.Service) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for _, item := range chunk {
|
||||||
|
switch item.Type {
|
||||||
|
case "http":
|
||||||
|
err := c.observeHttp(item, 0)
|
||||||
|
if err == nil {
|
||||||
|
addErr := c.statusRepository.Add(model.Status{
|
||||||
|
ServiceID: item.ID,
|
||||||
|
Status: model.StatusOK,
|
||||||
|
})
|
||||||
|
if addErr != nil {
|
||||||
|
log.Global.Get(log.CRON).Error("Add status to status repository fail", item.ID, addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, httpObserveFailed) {
|
||||||
|
addErr := c.statusRepository.Add(model.Status{
|
||||||
|
ServiceID: item.ID,
|
||||||
|
Status: model.StatusFailed,
|
||||||
|
})
|
||||||
|
if addErr != nil {
|
||||||
|
log.Global.Get(log.CRON).Error("Add status to status repository fail", item.ID, addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Global.Get(log.CRON).Error("Unexpected observe fail", item.ID, err)
|
||||||
|
|
||||||
|
break
|
||||||
|
case "tcp":
|
||||||
|
log.Global.Get(log.CRON).Warn("Cannot handle service with id: ", item.ID, ", because a tcp observer not implemented.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c check) observeHttp(service model.Service, attempt int) error {
|
||||||
|
if attempt >= 20 {
|
||||||
|
return httpObserveMaxTries
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempt > 5 {
|
||||||
|
err := c.statusRepository.Add(model.Status{
|
||||||
|
ServiceID: service.ID,
|
||||||
|
Status: model.StatusWarn,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.TypeConfig == nil || service.TypeConfig.HTTPConfig == nil {
|
||||||
|
return errors.New("service has no config for http")
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := service.TypeConfig.HTTPConfig
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: time.Second * 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequest(conf.Method, service.Host, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for s := range conf.Headers {
|
||||||
|
request.Header.Set(s, conf.Headers[s])
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
var (
|
||||||
|
e *url.Error
|
||||||
|
opErr *net.OpError
|
||||||
|
)
|
||||||
|
if errors.As(err, &e) && errors.As(e.Err, &opErr) {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
return c.observeHttp(service, attempt+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return c.observeHttp(service, attempt+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return httpObserveFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
AppVersion string
|
|
||||||
AppStartTime time.Time
|
AppStartTime time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,10 +17,6 @@ func init() {
|
|||||||
appLog.SetGlobalManager(appLog.NewManager())
|
appLog.SetGlobalManager(appLog.NewManager())
|
||||||
appLog.Global.Put(appLog.SYSTEM, logrus.New())
|
appLog.Global.Put(appLog.SYSTEM, logrus.New())
|
||||||
|
|
||||||
if AppVersion == "" {
|
|
||||||
AppVersion = "dev"
|
|
||||||
}
|
|
||||||
|
|
||||||
AppStartTime = time.Now()
|
AppStartTime = time.Now()
|
||||||
|
|
||||||
env := os.Getenv("APP_ENV")
|
env := os.Getenv("APP_ENV")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package version
|
package version
|
||||||
|
|
||||||
func AppVersion() string {
|
func AppVersion() string {
|
||||||
return "0.0.1"
|
return "0.0.3"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user