123 lines
3.8 KiB
Go
123 lines
3.8 KiB
Go
package operations
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"sort"
|
|
"time"
|
|
|
|
"proxy-pool/internal/config"
|
|
"proxy-pool/internal/controller/admin"
|
|
"proxy-pool/internal/controller/provider"
|
|
"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
|
|
providerStats provider.StatsReader
|
|
now func() time.Time
|
|
}
|
|
|
|
var _ admin.OperationalStatusReader = (*Reader)(nil)
|
|
|
|
func NewReader(
|
|
configuration ConfigurationReader,
|
|
inventory activitypool.StateInventoryReader,
|
|
now func() time.Time,
|
|
stats ...provider.StatsReader,
|
|
) (*Reader, error) {
|
|
if nilInterface(configuration) || nilInterface(inventory) || now == nil || len(stats) > 1 ||
|
|
(len(stats) == 1 && nilInterface(stats[0])) {
|
|
return nil, ErrInvalidReader
|
|
}
|
|
reader := &Reader{configuration: configuration, inventory: inventory, now: now}
|
|
if len(stats) == 1 {
|
|
reader.providerStats = stats[0]
|
|
}
|
|
return reader, 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))}
|
|
providerStats := make([]provider.Stats, len(upstreamIDs))
|
|
if reader.providerStats != nil {
|
|
providerStats = reader.providerStats.ReadProviderStats(upstreamIDs)
|
|
if len(providerStats) != len(upstreamIDs) {
|
|
return admin.OperationalStatus{}, ErrUnavailable
|
|
}
|
|
}
|
|
for index, inventory := range inventories {
|
|
if inventory.UpstreamID != upstreamIDs[index] || invalidInventory(inventory) ||
|
|
providerStats[index].UpstreamID != "" && providerStats[index].UpstreamID != upstreamIDs[index] {
|
|
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,
|
|
ConsecutiveEmptyFetch: providerStats[index].ConsecutiveEmptyFetch,
|
|
FetchErrorCount: providerStats[index].FetchErrorCount,
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|