package bootstrap import ( "context" "errors" "strings" "time" "github.com/jackc/pgx/v5/pgxpool" "github.com/redis/go-redis/v9" "proxy-pool/internal/adapters/postgresadmin" "proxy-pool/internal/adapters/redisactivity" "proxy-pool/internal/config" "proxy-pool/internal/platform/credentials" platformMetrics "proxy-pool/internal/platform/metrics" ) const ( redisNamespace = "controller" redisOperationTTL = 30 * time.Second redisMinimumScan = 4_096 redisCleanupLimit = 1_024 ) var ( ErrPostgresConfiguration = errors.New("invalid PostgreSQL configuration") ErrPostgresUnavailable = errors.New("PostgreSQL unavailable") ErrRedisConfiguration = errors.New("invalid Redis configuration") ErrRedisUnavailable = errors.New("Redis unavailable") ) type productionInfrastructure struct{} func (*productionInfrastructure) Open( ctx context.Context, configuration *config.Config, ) (_ ports, resultErr error) { if ctx == nil || configuration == nil { return ports{}, ErrInvalidOptions } var postgresPool *pgxpool.Pool var redisClient *redis.Client closeResources := func() error { var closeErr error if redisClient != nil { closeErr = redisClient.Close() } if postgresPool != nil { postgresPool.Close() } return closeErr } defer func() { if resultErr != nil { _ = closeResources() } }() opened := ports{close: closeResources} if configuration.Admin.Enabled { if strings.TrimSpace(configuration.Storage.PostgresURL) == "" { return ports{}, ErrPostgresConfiguration } poolConfig, err := pgxpool.ParseConfig(configuration.Storage.PostgresURL) if err != nil { return ports{}, ErrPostgresConfiguration } poolConfig.ConnConfig.RuntimeParams["application_name"] = "proxy-controller" postgresPool, err = pgxpool.NewWithConfig(ctx, poolConfig) if err != nil { return ports{}, ErrPostgresUnavailable } if err = postgresPool.Ping(ctx); err != nil { return ports{}, contextOr(ctx, ErrPostgresUnavailable) } if err = postgresadmin.ApplyMigrations(ctx, postgresPool); err != nil { return ports{}, err } opened.state, err = postgresadmin.New(postgresPool) if err != nil { return ports{}, err } } if configuration.Distribution.Enabled || configuration.Admin.Enabled { if strings.TrimSpace(configuration.Storage.RedisURL) == "" { return ports{}, ErrRedisConfiguration } redisOptions, err := redis.ParseURL(configuration.Storage.RedisURL) if err != nil { return ports{}, ErrRedisConfiguration } redisClient = redis.NewClient(redisOptions) if err = redisClient.Ping(ctx).Err(); err != nil { return ports{}, contextOr(ctx, ErrRedisUnavailable) } credentialStore, err := credentials.NewMemoryStore(credentialCapacity(configuration)) if err != nil { return ports{}, err } adapter, err := redisactivity.New(redisClient, redisactivity.Options{ Namespace: redisNamespace, Credentials: credentialStore, OperationTTL: redisOperationTTL, MaxCandidateScan: candidateScan(configuration), MaxRuntimeCounters: credentialCapacity(configuration), MaxInventoryScan: credentialCapacity(configuration), CleanupLimit: redisCleanupLimit, }) if err != nil { return ports{}, err } opened.activity = adapter opened.readiness = redisReadiness{client: redisClient} } if configuration.Metrics.Enabled { opened.metricsReadiness = selectMetricsReadiness( configuration, storeReadiness{postgres: postgresPool, redis: redisClient}, redisReadiness{client: redisClient}, ) } return opened, nil } func selectMetricsReadiness( configuration *config.Config, admin, activity platformMetrics.ReadinessChecker, ) platformMetrics.ReadinessChecker { if configuration.Distribution.Enabled { return activity } if configuration.Admin.Enabled { return admin } return alwaysReady{} } type alwaysReady struct{} func (alwaysReady) Ready(ctx context.Context) error { if ctx == nil { return ErrInvalidOptions } return ctx.Err() } type storeReadiness struct { postgres *pgxpool.Pool redis *redis.Client } func (readiness storeReadiness) Ready(ctx context.Context) error { if ctx == nil { return ErrInvalidOptions } if readiness.postgres != nil { if err := readiness.postgres.Ping(ctx); err != nil { return contextOr(ctx, ErrPostgresUnavailable) } } if readiness.redis != nil { if err := readiness.redis.Ping(ctx).Err(); err != nil { return contextOr(ctx, ErrRedisUnavailable) } } return nil } type redisReadiness struct { client *redis.Client } func (readiness redisReadiness) Ready(ctx context.Context) error { if ctx == nil || readiness.client == nil { return ErrRedisUnavailable } if err := readiness.client.Ping(ctx).Err(); err != nil { return contextOr(ctx, ErrRedisUnavailable) } return nil } func credentialCapacity(configuration *config.Config) int { capacity := 0 maximum := int(^uint(0) >> 1) for _, upstream := range configuration.Upstreams { if upstream.Pool.MaxSize <= 0 { continue } if capacity > maximum-upstream.Pool.MaxSize { return maximum } capacity += upstream.Pool.MaxSize } if capacity == 0 { return 1 } return capacity } func candidateScan(configuration *config.Config) int { configured := configuration.Distribution.Extraction if configured.MaxCountPerRequest > int(^uint(0)>>1)-configured.ReserveForGateway { return int(^uint(0) >> 1) } value := configured.MaxCountPerRequest + configured.ReserveForGateway if value < redisMinimumScan { return redisMinimumScan } return value } func contextOr(ctx context.Context, fallback error) error { if ctx != nil && ctx.Err() != nil { return ctx.Err() } return fallback }