Compare commits

...

2 Commits

Author SHA1 Message Date
bb5796ab8c doc: add layer endpoints
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
2024-04-19 09:28:46 +02:00
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
9 changed files with 264 additions and 40 deletions

View File

@ -5,14 +5,16 @@
### Déploiement mono-noeud ### Déploiement mono-noeud
![](../resources/deployment_fr.png) ![](../resources/deployment_fr.png)
## Terminologie ## Terminologie
Voici une liste des termes utilisés dans le lexique Bouncer. Voici une liste des termes utilisés dans le lexique Bouncer.
### Proxy ### Proxy
Un "proxy" est une entité logique définissant le relation suivante: Un "proxy" est une entité logique définissant le relation suivante:
- Un ou plusieurs patrons de filtrage sous la forme `<host>:<port>`. Ceux ci identifient le ou les domaines associés à l'entité; - Un ou plusieurs patrons de filtrage sous la forme d'un patron d'URL avec le caractère `*` comme caractère générique. Ceux ci identifient le ou les domaines/chemins associés à l'entité;
- Une URL cible qui servira de base pour la réécriture des requêtes. - Une URL cible qui servira de base pour la réécriture des requêtes.
Un "proxy" peut avoir zéro ou plusieurs "layers" associés. Un "proxy" peut avoir zéro ou plusieurs "layers" associés.

View File

@ -147,7 +147,9 @@ Lister les proxies existants
{ {
"name": "myproxy", "name": "myproxy",
"weight": 0, "weight": 0,
"enabled": false "enabled": false,
"createdAt": "2018-12-10T13:45:00.000Z",
"updatedAt": "2018-12-10T13:45:00.000Z"
} }
] ]
} }
@ -179,3 +181,169 @@ Supprimer le proxy
#### Source #### Source
Voir [`internal/admin/proxy_route.go#deleteProxy()`](../../../internal/admin/proxy_route.go#deleteProxy) Voir [`internal/admin/proxy_route.go#deleteProxy()`](../../../internal/admin/proxy_route.go#deleteProxy)
### `POST /api/v1/proxies/{proxyName}/layers`
Créer un nouveau layer pour un proxy donné
#### Paramètres
- `{proxyName}` - Nom du proxy sur lequel créer le layer
#### Exemple de corps de requête
```json
{
"name": "mylayer", // OBLIGATOIRE - Nom du layer
"type": "<layer_type>", // OBLIGATOIRE - Type du layer, voir doc/fr/references/layers
"options": {} // OPTIONNEL - Options associées au layer, voir doc/fr/references/layers
}
```
#### Exemple de résultat
```json
{
"data": {
"layer": {
"name": "mylayer",
"type": "<layer_type>",
"enabled": false,
"weight": 0,
"options": {},
"createdAt": "2018-12-10T13:45:00.000Z",
"updatedAt": "2018-12-10T13:45:00.000Z"
}
}
}
```
#### Source
Voir [`internal/admin/layer_route.go#createLayer()`](../../../internal/admin/layer_route.go#createLayer)
### `GET /api/v1/proxies/{proxyName}/layers/{layerName}`
Récupérer les informations complètes sur un layer
#### Paramètres
- `{proxyName}` - Nom du proxy parent
- `{layerName}` - Nom du layer
#### Exemple de résultat
```json
{
"data": {
"layer": {
"name": "mylayer",
"type": "<layer_type>",
"enabled": false,
"weight": 0,
"options": {},
"createdAt": "2018-12-10T13:45:00.000Z",
"updatedAt": "2018-12-10T13:45:00.000Z"
}
}
}
```
#### Source
Voir [`internal/admin/layer_route.go#getLayer()`](../../../internal/admin/layer_route.go#getLayer)
### `PUT /api/v1/proxies/{proxyName}/layers/{layerName}`
Modifier un layer
#### Paramètres
- `{proxyName}` - Nom du proxy parent
- `{layerName}` - Nom du layer
#### Exemple de corps de requête
```json
{
"weight": 100, // OPTIONNEL - Poids à associer au layer
"enabled": true, // OPTIONNEL - Activer/désactiver le layer
"options": {} // OPTIONNEL - Modifier les options associées au layer, voir doc/fr/references/layers
}
```
#### Exemple de résultat
```json
{
"data": {
"layer": {
"name": "mylayer",
"type": "<layer_type>",
"enabled": false,
"weight": 0,
"options": {},
"createdAt": "2018-12-10T13:45:00.000Z",
"updatedAt": "2018-12-10T13:45:00.000Z"
}
}
}
```
#### Source
Voir [`internal/admin/layer_route.go#updateLayer()`](../../../internal/admin/layer_route.go#updateLayer)
### `GET /api/v1/proxies/{proxyName}/layers?names={name1,name2,...}`
Lister les layers existants
#### Paramètres
- `{proxyName}` - Nom du proxy parent
- `{names}` - Optionnel - Liste des noms de proxy à appliquer en tant que filtre
#### Exemple de résultat
```json
{
"data": {
"layers": [
{
"name": "mylayer",
"weight": 0,
"enabled": false,
"createdAt": "2018-12-10T13:45:00.000Z",
"updatedAt": "2018-12-10T13:45:00.000Z"
}
]
}
}
```
#### Source
Voir [`internal/admin/layer_route.go#queryLayers()`](../../../internal/admin/layer_route.go#queryLayers)
## `DELETE /api/v1/proxies/{proxyName}/layers/{layerName}`
Supprimer le layer
#### Paramètres
- `{proxyName}` - Nom du proxy parent
- `{layerName}` - Nom du layer
#### Exemple de résultat
```json
{
"data": {
"layerName": "mylayer"
}
}
```
#### Source
Voir [`internal/admin/layer_route.go#deleteLayer()`](../../../internal/admin/layer_route.go#deleteLayer)

View File

@ -22,7 +22,7 @@ func (s *Server) bootstrapProxies(ctx context.Context) error {
layerRepo := s.layerRepository layerRepo := s.layerRepository
lockTimeout := time.Duration(s.bootstrapConfig.LockTimeout) 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 { err := locker.WithLock(ctx, "bouncer-admin-bootstrap", lockTimeout, func(ctx context.Context) error {
logger.Info(ctx, "bootstrapping proxies") logger.Info(ctx, "bootstrapping proxies")

View File

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

View File

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

View File

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

@ -28,7 +28,7 @@ func SetupIntegrations(ctx context.Context, conf *config.Config) ([]integration.
func setupKubernetesIntegration(ctx context.Context, conf *config.Config) (*kubernetes.Integration, error) { func setupKubernetesIntegration(ctx context.Context, conf *config.Config) (*kubernetes.Integration, error) {
client := newRedisClient(conf.Redis) client := newRedisClient(conf.Redis)
locker := redis.NewLocker(client) locker := redis.NewLocker(client, 10)
integration := kubernetes.NewIntegration( integration := kubernetes.NewIntegration(
kubernetes.WithReaderTokenSecret(string(conf.Integrations.Kubernetes.ReaderTokenSecret)), 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) { func SetupLocker(ctx context.Context, conf *config.Config) (lock.Locker, error) {
client := newRedisClient(conf.Redis) client := newRedisClient(conf.Redis)
locker := redis.NewLocker(client) locker := redis.NewLocker(client, int(conf.Redis.LockMaxRetries))
return locker, nil return locker, nil
} }