Compare commits

..

3 Commits

Author SHA1 Message Date
83fcb9a39d feat: add limited retry mechanism to prevent startup error if redis is not ready
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
2024-04-05 10:30:34 +02:00
ad907576dc fix: move log message
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
2024-03-29 11:13:05 +01:00
3a894972f1 doc: enable json highlighting in reference examples
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
2024-03-29 09:36:36 +01:00
9 changed files with 153 additions and 99 deletions

View File

@ -25,7 +25,6 @@ Où:
- droit en lecture sur l'ensemble des entités (proxy, layer);
- droit en lecture ET en écriture sur l'ensemble des entités.
## Points d'entrée
### `POST /api/v1/proxies`
@ -34,7 +33,7 @@ Créer un nouveau proxy
#### Exemple de corps de requête
```json5
```json
{
"name": "myproxy", // OBLIGATOIRE - Nom du proxy
"to": "https://www.cadoles.com", // OBLIGATOIRE - Site distant ciblé par le proxy
@ -44,7 +43,7 @@ Créer un nouveau proxy
#### Exemple de résultat
```json5
```json
{
"data": {
"proxy": {
@ -74,7 +73,7 @@ Récupérer les informations complètes sur un proxy
#### Exemple de résultat
```json5
```json
{
"data": {
"proxy": {
@ -100,18 +99,18 @@ Modifier un proxy
#### Exemple de corps de requête
```json5
```json
{
"to": "https://www.cadoles.com", // OPTIONNEL - Site distant ciblé par le proxy
"from": ["mylocalproxydomain:*"], // OPTIONNEL - Liste de patrons de filtrage associés au proxy
"weight": 100, // OPTIONNEL - Poids à associer au proxy
"enabled": true, // OPTIONNEL - Activer/désactiver le proxy
"enabled": true // OPTIONNEL - Activer/désactiver le proxy
}
```
#### Exemple de résultat
```json5
```json
{
"data": {
"proxy": {
@ -141,14 +140,14 @@ Lister les proxies existants
#### Exemple de résultat
```json5
```json
{
"data": {
"proxies": [
{
"name": "myproxy",
"weight": 0,
"enabled": false,
"enabled": false
}
]
}
@ -169,7 +168,7 @@ Supprimer le proxy
#### Exemple de résultat
```json5
```json
{
"data": {
"proxyName": "myproxy"

View File

@ -22,7 +22,7 @@ func (s *Server) bootstrapProxies(ctx context.Context) error {
layerRepo := s.layerRepository
lockTimeout := time.Duration(s.bootstrapConfig.LockTimeout)
locker := redis.NewLocker(s.redisClient)
locker := redis.NewLocker(s.redisClient, int(s.bootstrapConfig.MaxConnectionRetries))
err := locker.WithLock(ctx, "bouncer-admin-bootstrap", lockTimeout, func(ctx context.Context) error {
logger.Info(ctx, "bootstrapping proxies")

View File

@ -15,6 +15,7 @@ type BootstrapConfig struct {
Proxies map[store.ProxyName]BootstrapProxyConfig `yaml:"proxies"`
Dir InterpolatedString `yaml:"dir"`
LockTimeout InterpolatedDuration `yaml:"lockTimeout"`
MaxConnectionRetries InterpolatedInt `yaml:"maxRetries"`
}
func (c *BootstrapConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
@ -64,6 +65,7 @@ func NewDefaultBootstrapConfig() BootstrapConfig {
return BootstrapConfig{
Dir: "",
LockTimeout: *NewInterpolatedDuration(30 * time.Second),
MaxConnectionRetries: 10,
}
}

View File

@ -14,6 +14,7 @@ type RedisConfig struct {
ReadTimeout InterpolatedDuration `yaml:"readTimeout"`
WriteTimeout InterpolatedDuration `yaml:"writeTimeout"`
DialTimeout InterpolatedDuration `yaml:"dialTimeout"`
LockMaxRetries InterpolatedInt `yaml:"lockMaxRetries"`
}
func NewDefaultRedisConfig() RedisConfig {
@ -23,5 +24,6 @@ func NewDefaultRedisConfig() RedisConfig {
ReadTimeout: InterpolatedDuration(30 * time.Second),
WriteTimeout: InterpolatedDuration(30 * time.Second),
DialTimeout: InterpolatedDuration(30 * time.Second),
LockMaxRetries: 10,
}
}

View File

@ -13,7 +13,7 @@ import (
type Locker struct {
client redis.UniversalClient
timeout time.Duration
maxRetries int
}
// WithLock implements lock.Locker.
@ -26,6 +26,7 @@ func (l *Locker) WithLock(ctx context.Context, key string, timeout time.Duration
logger.Debug(ctx, "acquiring lock")
err := retryWithBackoff(ctx, l.maxRetries, func(ctx context.Context) error {
lock, err := locker.Obtain(ctx, key, timeout, &redislock.Options{
RetryStrategy: backoff,
})
@ -47,12 +48,19 @@ func (l *Locker) WithLock(ctx context.Context, key string, timeout time.Duration
return errors.WithStack(err)
}
return nil
})
if err != nil {
return errors.WithStack(err)
}
return nil
}
func NewLocker(client redis.UniversalClient) *Locker {
func NewLocker(client redis.UniversalClient, maxRetries int) *Locker {
return &Locker{
client: client,
maxRetries: maxRetries,
}
}

View File

@ -0,0 +1,42 @@
package redis
import (
"context"
"time"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
const (
baseWatchBackoffDelay = time.Millisecond * 500
maxDelay = time.Minute * 10
)
func retryWithBackoff(ctx context.Context, attempts int, fn func(ctx context.Context) error) error {
backoffDelay := baseWatchBackoffDelay
count := 0
for {
err := fn(ctx)
if err == nil {
return nil
}
err = errors.WithStack(err)
count++
if count >= attempts {
return errors.Wrapf(err, "execution failed after %d attempts", attempts)
}
logger.Error(ctx, "error while executing func, retrying with backoff", logger.E(err), logger.F("backoffDelay", backoffDelay), logger.F("remainingAttempts", attempts-count))
time.Sleep(backoffDelay)
backoffDelay *= 2
if backoffDelay > maxDelay {
backoffDelay = maxDelay
}
}
}

View File

@ -40,12 +40,13 @@ MAIN:
logger.F("from", from),
)
if matches := wildcard.Match(url.String(), from); !matches {
continue
}
logger.Debug(
ctx, "proxy's from matched",
logger.F("from", from),
)
continue
}
match = p
break MAIN

View File

@ -28,7 +28,7 @@ func SetupIntegrations(ctx context.Context, conf *config.Config) ([]integration.
func setupKubernetesIntegration(ctx context.Context, conf *config.Config) (*kubernetes.Integration, error) {
client := newRedisClient(conf.Redis)
locker := redis.NewLocker(client)
locker := redis.NewLocker(client, 10)
integration := kubernetes.NewIntegration(
kubernetes.WithReaderTokenSecret(string(conf.Integrations.Kubernetes.ReaderTokenSecret)),

View File

@ -10,6 +10,6 @@ import (
func SetupLocker(ctx context.Context, conf *config.Config) (lock.Locker, error) {
client := newRedisClient(conf.Redis)
locker := redis.NewLocker(client)
locker := redis.NewLocker(client, int(conf.Redis.LockMaxRetries))
return locker, nil
}