proxy-pool/internal/controller/operations/reader.go
2026-07-30 11:38:33 +08:00

105 lines
3.0 KiB
Go

package operations
import (
"context"
"errors"
"reflect"
"sort"
"time"
"proxy-pool/internal/config"
"proxy-pool/internal/controller/admin"
"proxy-pool/internal/domain/activitypool"
)
var (
ErrInvalidReader = errors.New("invalid operational status reader")
ErrUnavailable = errors.New("operational status unavailable")
)
type ConfigurationReader interface {
Current() *config.Config
}
type Reader struct {
configuration ConfigurationReader
inventory activitypool.StateInventoryReader
now func() time.Time
}
var _ admin.OperationalStatusReader = (*Reader)(nil)
func NewReader(
configuration ConfigurationReader,
inventory activitypool.StateInventoryReader,
now func() time.Time,
) (*Reader, error) {
if nilInterface(configuration) || nilInterface(inventory) || now == nil {
return nil, ErrInvalidReader
}
return &Reader{configuration: configuration, inventory: inventory, now: now}, nil
}
func (reader *Reader) ReadOperationalStatus(ctx context.Context) (admin.OperationalStatus, error) {
if ctx == nil || reader == nil {
return admin.OperationalStatus{}, ErrInvalidReader
}
if err := ctx.Err(); err != nil {
return admin.OperationalStatus{}, err
}
configuration := reader.configuration.Current()
if configuration == nil {
return admin.OperationalStatus{}, ErrUnavailable
}
upstreamIDs := make([]string, 0, len(configuration.Upstreams))
for upstreamID := range configuration.Upstreams {
upstreamIDs = append(upstreamIDs, upstreamID)
}
sort.Strings(upstreamIDs)
inventories, err := reader.inventory.ReadStateInventory(ctx, upstreamIDs, reader.now().UTC())
if err != nil {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return admin.OperationalStatus{}, err
}
return admin.OperationalStatus{}, errors.Join(ErrUnavailable, err)
}
if len(inventories) != len(upstreamIDs) {
return admin.OperationalStatus{}, ErrUnavailable
}
status := admin.OperationalStatus{Upstreams: make([]admin.UpstreamActivity, len(inventories))}
for index, inventory := range inventories {
if inventory.UpstreamID != upstreamIDs[index] || invalidInventory(inventory) {
return admin.OperationalStatus{}, ErrUnavailable
}
status.Upstreams[index] = admin.UpstreamActivity{
Name: inventory.UpstreamID,
Available: inventory.Available,
Checking: inventory.Checking,
Suspect: inventory.Suspect,
Draining: inventory.Draining,
Extracted: inventory.Extracted,
}
}
return status, nil
}
func invalidInventory(inventory activitypool.StateInventory) bool {
return inventory.Fetched < 0 || inventory.Checking < 0 || inventory.Available < 0 ||
inventory.Suspect < 0 || inventory.Draining < 0 || inventory.Unhealthy < 0 || inventory.Extracted < 0
}
func nilInterface(value any) bool {
if value == nil {
return true
}
reflected := reflect.ValueOf(value)
switch reflected.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return reflected.IsNil()
default:
return false
}
}