121 lines
4.0 KiB
Go
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
|
|
}
|
|
}
|