proxy-pool/internal/checker/bootstrap/bootstrap.go
youfak 2bdc1ebda3
Some checks are pending
ci / proto (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
ci / integration (push) Waiting to run
feat: add checker task executor
2026-07-31 21:12:32 +08:00

121 lines
4.0 KiB
Go

// Package bootstrap assembles the standalone proxy-checker process. Checker
// execution is intentionally isolated from Gateway request handling and from
// all Redis/PostgreSQL access.
package bootstrap
import (
"bytes"
"context"
"errors"
"fmt"
"reflect"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
controlplanev1 "proxy-pool/gen/controlplane/v1"
"proxy-pool/internal/checker/controlplane"
"proxy-pool/internal/checker/probe"
"proxy-pool/internal/config"
"proxy-pool/internal/controlplane/clienttransport"
"proxy-pool/internal/domain/workerruntime"
)
var (
ErrInvalidOptions = errors.New("invalid checker bootstrap options")
ErrStartup = errors.New("checker startup failed")
)
type Options struct {
ConfigPath string
Resolver config.Resolver
ControlPlaneAddress string
CheckerID string
InstanceID string
MaxInFlight int
SupportedLevels []controlplanev1.CheckLevel
GRPCTransport credentials.TransportCredentials
}
func Run(ctx context.Context, options Options) error {
if err := validateOptions(ctx, options); err != nil {
return err
}
configuration, err := loadConfiguration(ctx, options.ConfigPath, options.Resolver)
if err != nil {
return fmt.Errorf("%w: load configuration: %w", ErrStartup, err)
}
if !configuration.ControlPlane.Enabled {
return errors.Join(ErrInvalidOptions, errors.New("controlPlane must be enabled"))
}
transport, err := clienttransport.New(
configuration.ControlPlane, options.ControlPlaneAddress, configuration.ControlPlane.CheckerTLS, options.GRPCTransport, "checkerTLS",
)
if err != nil {
return fmt.Errorf("%w: %w", ErrInvalidOptions, err)
}
connection, err := grpc.NewClient(options.ControlPlaneAddress,
grpc.WithTransportCredentials(transport),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(configuration.ControlPlane.MaxMessageBytes),
grpc.MaxCallSendMsgSize(configuration.ControlPlane.MaxMessageBytes),
),
)
if err != nil {
return fmt.Errorf("%w: dial control plane: %w", ErrStartup, err)
}
defer connection.Close()
client := controlplane.NewGeneratedClient(controlplanev1.NewCheckerControlPlaneClient(connection))
runner, err := controlplane.NewRunner(client, probe.NewExecutor(), controlplane.Options{
CheckerID: options.CheckerID, InstanceID: options.InstanceID, MaxInFlight: options.MaxInFlight,
SupportedLevels: append([]controlplanev1.CheckLevel(nil), options.SupportedLevels...),
ReportBatchSize: options.MaxInFlight,
})
if err != nil {
return fmt.Errorf("%w: build checker runner: %w", ErrStartup, err)
}
return runner.Run(ctx)
}
func validateOptions(ctx context.Context, options Options) error {
if ctx == nil || strings.TrimSpace(options.ConfigPath) != options.ConfigPath || options.ConfigPath == "" ||
nilInterface(options.Resolver) || !clienttransport.ValidDialAddress(options.ControlPlaneAddress) ||
!workerruntime.ValidIdentifier(options.CheckerID) || !workerruntime.ValidIdentifier(options.InstanceID) ||
options.MaxInFlight <= 0 || len(options.SupportedLevels) == 0 {
return ErrInvalidOptions
}
return nil
}
func loadConfiguration(ctx context.Context, path string, resolver config.Resolver) (*config.Config, error) {
if ctx == nil || strings.TrimSpace(path) != path || path == "" || nilInterface(resolver) {
return nil, ErrInvalidOptions
}
if err := ctx.Err(); err != nil {
return nil, err
}
content, err := resolver.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read configuration %q: %w", path, err)
}
configuration, err := config.LoadResolved(bytes.NewReader(content), resolver)
if err != nil {
return nil, fmt.Errorf("decode configuration %q: %w", path, err)
}
return configuration, nil
}
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
}
}