Compare commits
6 Commits
v2024.6.27
...
v2024.6.28
Author | SHA1 | Date | |
---|---|---|---|
c73fe8cca5 | |||
3c1939f418 | |||
3565618335 | |||
64ca8fe1e4 | |||
d5669a4eb5 | |||
f3aa8b9be6 |
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
- [(FR) - Layers](./fr/references/layers/README.md)
|
- [(FR) - Layers](./fr/references/layers/README.md)
|
||||||
- [(FR) - Métriques](./fr/references/metrics.md)
|
- [(FR) - Métriques](./fr/references/metrics.md)
|
||||||
- [(FR) - Fichier de configuration](../misc/packaging/common/config.yml)
|
- [(FR) - Configuration](./fr/references/configuration.md)
|
||||||
- [(FR) - API d'administration](./fr/references/admin_api.md)
|
- [(FR) - API d'administration](./fr/references/admin_api.md)
|
||||||
|
|
||||||
## Tutoriels
|
## Tutoriels
|
||||||
@ -29,3 +29,4 @@
|
|||||||
|
|
||||||
- [(FR) - Démarrer avec les sources](./fr/tutorials/getting-started-with-sources.md)
|
- [(FR) - Démarrer avec les sources](./fr/tutorials/getting-started-with-sources.md)
|
||||||
- [(FR) - Créer son propre layer](./fr/tutorials/create-custom-layer.md)
|
- [(FR) - Créer son propre layer](./fr/tutorials/create-custom-layer.md)
|
||||||
|
- [(FR) - Étudier les performances de Bouncer](./fr/tutorials/profiling.md)
|
||||||
|
34
doc/fr/references/configuration.md
Normal file
34
doc/fr/references/configuration.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Configuration
|
||||||
|
|
||||||
|
## Référence
|
||||||
|
|
||||||
|
Vous trouverez ici un fichier de configuration de référence, complet et commenté:
|
||||||
|
|
||||||
|
[`misc/packaging/common/config.yml`](../../../misc/packaging/common/config.yml)
|
||||||
|
|
||||||
|
## Interpolation de variables
|
||||||
|
|
||||||
|
Il est possible d'utiliser de l'interpolation de variables d'environnement dans le fichier de configuration via la syntaxe `${var}`.
|
||||||
|
|
||||||
|
Les fonctions d'interpolation suivantes sont également disponibles:
|
||||||
|
|
||||||
|
- `${var^}`
|
||||||
|
- `${var^^}`
|
||||||
|
- `${var,}`
|
||||||
|
- `${var,,}`
|
||||||
|
- `${var:position}`
|
||||||
|
- `${var:position:length}`
|
||||||
|
- `${var#substring}`
|
||||||
|
- `${var##substring}`
|
||||||
|
- `${var%substring}`
|
||||||
|
- `${var%%substring}`
|
||||||
|
- `${var/substring/replacement}`
|
||||||
|
- `${var//substring/replacement}`
|
||||||
|
- `${var/#substring/replacement}`
|
||||||
|
- `${var/%substring/replacement}`
|
||||||
|
- `${#var}`
|
||||||
|
- `${var=default}`
|
||||||
|
- `${var:=default}`
|
||||||
|
- `${var:-default}`
|
||||||
|
|
||||||
|
_Voir le package [`github.com/drone/envsubst`](https://pkg.go.dev/github.com/drone/envsubst) pour plus de détails._
|
@ -66,7 +66,21 @@ La requête en cours de traitement.
|
|||||||
{
|
{
|
||||||
method: "string", // Méthode HTTP
|
method: "string", // Méthode HTTP
|
||||||
host: "string", // Nom d'hôte (`Host`) associé à la requête
|
host: "string", // Nom d'hôte (`Host`) associé à la requête
|
||||||
url: "string", // URL associée à la requête
|
url: { // URL associée à la requête sous sa forme structurée
|
||||||
|
"scheme": "string", // Schéma HTTP de l'URL
|
||||||
|
"opaque": "string", // Données opaque de l'URL
|
||||||
|
"user": { // Identifiants d'URL (Basic Auth)
|
||||||
|
"username": "",
|
||||||
|
"password": ""
|
||||||
|
},
|
||||||
|
"host": "string", // Nom d'hôte (<domaine>:<port>) de l'URL
|
||||||
|
"path": "string", // Chemin de l'URL (format assaini)
|
||||||
|
"rawPath": "string", // Chemin de l'URL (format brut)
|
||||||
|
"rawQuery": "string", // Variables d'URL (format brut)
|
||||||
|
"fragment" : "string", // Fragment d'URL (format assaini)
|
||||||
|
"rawFragment" : "string" // Fragment d'URL (format brut)
|
||||||
|
},
|
||||||
|
rawUrl: "string", // URL associée à la requête (format assaini)
|
||||||
proto: "string", // Numéro de version du protocole utilisé
|
proto: "string", // Numéro de version du protocole utilisé
|
||||||
protoMajor: "int", // Numéro de version majeure du protocole utilisé
|
protoMajor: "int", // Numéro de version majeure du protocole utilisé
|
||||||
protoMinor: "int", // Numéro de version mineur du protocole utilisé
|
protoMinor: "int", // Numéro de version mineur du protocole utilisé
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Analyser les performances de Bouncer
|
# Étudier les performances de Bouncer
|
||||||
|
|
||||||
1. Lancer un benchmark du proxy
|
1. Lancer un benchmark du proxy
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ func layerHeaderHints(outputMode format.OutputMode) format.Hints {
|
|||||||
format.NewProp("Type", "Type"),
|
format.NewProp("Type", "Type"),
|
||||||
format.NewProp("Enabled", "Enabled"),
|
format.NewProp("Enabled", "Enabled"),
|
||||||
format.NewProp("Weight", "Weight"),
|
format.NewProp("Weight", "Weight"),
|
||||||
|
format.NewProp("Revision", "Revision"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,6 +26,7 @@ func layerHints(outputMode format.OutputMode) format.Hints {
|
|||||||
format.NewProp("Type", "Type"),
|
format.NewProp("Type", "Type"),
|
||||||
format.NewProp("Enabled", "Enabled"),
|
format.NewProp("Enabled", "Enabled"),
|
||||||
format.NewProp("Weight", "Weight"),
|
format.NewProp("Weight", "Weight"),
|
||||||
|
format.NewProp("Revision", "Revision"),
|
||||||
format.NewProp("Options", "Options"),
|
format.NewProp("Options", "Options"),
|
||||||
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||||
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||||
|
@ -12,6 +12,7 @@ func proxyHeaderHints(outputMode format.OutputMode) format.Hints {
|
|||||||
format.NewProp("Name", "Name"),
|
format.NewProp("Name", "Name"),
|
||||||
format.NewProp("Enabled", "Enabled"),
|
format.NewProp("Enabled", "Enabled"),
|
||||||
format.NewProp("Weight", "Weight"),
|
format.NewProp("Weight", "Weight"),
|
||||||
|
format.NewProp("Revision", "Revision"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,6 +26,7 @@ func proxyHints(outputMode format.OutputMode) format.Hints {
|
|||||||
format.NewProp("To", "To"),
|
format.NewProp("To", "To"),
|
||||||
format.NewProp("Enabled", "Enabled"),
|
format.NewProp("Enabled", "Enabled"),
|
||||||
format.NewProp("Weight", "Weight"),
|
format.NewProp("Weight", "Weight"),
|
||||||
|
format.NewProp("Revision", "Revision"),
|
||||||
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||||
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,6 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -11,9 +10,6 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// var reVar = regexp.MustCompile(`^\${(\w+)}$`)
|
|
||||||
var reVar = regexp.MustCompile(`\${(.*?)}`)
|
|
||||||
|
|
||||||
type InterpolatedString string
|
type InterpolatedString string
|
||||||
|
|
||||||
func (is *InterpolatedString) UnmarshalYAML(value *yaml.Node) error {
|
func (is *InterpolatedString) UnmarshalYAML(value *yaml.Node) error {
|
||||||
@ -23,12 +19,13 @@ func (is *InterpolatedString) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
str, err := envsubst.EvalEnv(str)
|
||||||
*is = InterpolatedString(os.Getenv(match[1]))
|
if err != nil {
|
||||||
} else {
|
return errors.WithStack(err)
|
||||||
*is = InterpolatedString(str)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*is = InterpolatedString(str)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +38,9 @@ func (ii *InterpolatedInt) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
str, err := envsubst.EvalEnv(str)
|
||||||
str = os.Getenv(match[1])
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
intVal, err := strconv.ParseInt(str, 10, 32)
|
intVal, err := strconv.ParseInt(str, 10, 32)
|
||||||
@ -64,11 +62,12 @@ func (ifl *InterpolatedFloat) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
str, err := envsubst.EvalEnv(str)
|
||||||
str = os.Getenv(match[1])
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
floatVal, err := strconv.ParseFloat(str, 10)
|
floatVal, err := strconv.ParseFloat(str, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "could not parse float '%v', line '%d'", str, value.Line)
|
return errors.Wrapf(err, "could not parse float '%v', line '%d'", str, value.Line)
|
||||||
}
|
}
|
||||||
@ -87,8 +86,9 @@ func (ib *InterpolatedBool) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
str, err := envsubst.EvalEnv(str)
|
||||||
str = os.Getenv(match[1])
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
boolVal, err := strconv.ParseBool(str)
|
boolVal, err := strconv.ParseBool(str)
|
||||||
@ -165,22 +165,15 @@ type InterpolatedStringSlice []string
|
|||||||
|
|
||||||
func (iss *InterpolatedStringSlice) UnmarshalYAML(value *yaml.Node) error {
|
func (iss *InterpolatedStringSlice) UnmarshalYAML(value *yaml.Node) error {
|
||||||
var data []string
|
var data []string
|
||||||
var evErr error
|
|
||||||
|
|
||||||
if err := value.Decode(&data); err != nil {
|
if err := value.Decode(&data); err != nil {
|
||||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into map", value.Value, value.Line)
|
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into map", value.Value, value.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, value := range data {
|
for index, value := range data {
|
||||||
//match := reVar.FindStringSubmatch(value)
|
value, err := envsubst.EvalEnv(value)
|
||||||
re := regexp.MustCompile(`\${(.*?)}`)
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
res := re.FindAllStringSubmatch(value, 10)
|
|
||||||
if len(res) > 0 {
|
|
||||||
value, evErr = envsubst.EvalEnv(value)
|
|
||||||
if evErr != nil {
|
|
||||||
return evErr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data[index] = value
|
data[index] = value
|
||||||
@ -200,8 +193,9 @@ func (id *InterpolatedDuration) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
str, err := envsubst.EvalEnv(str)
|
||||||
str = os.Getenv(match[1])
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
duration, err := time.ParseDuration(str)
|
duration, err := time.ParseDuration(str)
|
||||||
|
@ -74,7 +74,7 @@ func (l *Layer) ResponseTransformer(layer *store.Layer) proxy.ResponseTransforme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Layer {
|
func New(funcs ...OptionFunc) *Layer {
|
||||||
return &Layer{}
|
return &Layer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
internal/proxy/director/layer/rewriter/options.go
Normal file
16
internal/proxy/director/layer/rewriter/options.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package rewriter
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionFunc func(opts *Options)
|
||||||
|
|
||||||
|
func NewOptions(funcs ...OptionFunc) *Options {
|
||||||
|
opts := &Options{}
|
||||||
|
|
||||||
|
for _, fn := range funcs {
|
||||||
|
fn(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
@ -12,9 +12,27 @@ type RequestEnv struct {
|
|||||||
Request RequestInfo `expr:"request"`
|
Request RequestInfo `expr:"request"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type URLEnv struct {
|
||||||
|
Scheme string `expr:"scheme"`
|
||||||
|
Opaque string `expr:"opaque"`
|
||||||
|
User UserInfoEnv `expr:"user"`
|
||||||
|
Host string `expr:"host"`
|
||||||
|
Path string `expr:"path"`
|
||||||
|
RawPath string `expr:"rawPath"`
|
||||||
|
RawQuery string `expr:"rawQuery"`
|
||||||
|
Fragment string `expr:"fragment"`
|
||||||
|
RawFragment string `expr:"rawFragment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoEnv struct {
|
||||||
|
Username string `expr:"username"`
|
||||||
|
Password string `expr:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
type RequestInfo struct {
|
type RequestInfo struct {
|
||||||
Method string `expr:"method"`
|
Method string `expr:"method"`
|
||||||
URL string `expr:"url"`
|
URL URLEnv `expr:"url"`
|
||||||
|
RawURL string `expr:"rawUrl"`
|
||||||
Proto string `expr:"proto"`
|
Proto string `expr:"proto"`
|
||||||
ProtoMajor int `expr:"protoMajor"`
|
ProtoMajor int `expr:"protoMajor"`
|
||||||
ProtoMinor int `expr:"protoMinor"`
|
ProtoMinor int `expr:"protoMinor"`
|
||||||
@ -33,18 +51,32 @@ func (l *Layer) applyRequestRules(r *http.Request, options *LayerOptions) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := rule.NewEngine[*RequestEnv](
|
engine, err := l.getRequestRuleEngine(r, options)
|
||||||
ruleHTTP.WithRequestFuncs(r),
|
|
||||||
rule.WithRules(options.Rules.Request...),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := &RequestEnv{
|
env := &RequestEnv{
|
||||||
Request: RequestInfo{
|
Request: RequestInfo{
|
||||||
Method: r.Method,
|
Method: r.Method,
|
||||||
URL: r.URL.String(),
|
URL: URLEnv{
|
||||||
|
Scheme: r.URL.Scheme,
|
||||||
|
Opaque: r.URL.Opaque,
|
||||||
|
User: UserInfoEnv{
|
||||||
|
Username: r.URL.User.Username(),
|
||||||
|
Password: func() string {
|
||||||
|
passwd, _ := r.URL.User.Password()
|
||||||
|
return passwd
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
Host: r.URL.Host,
|
||||||
|
Path: r.URL.Path,
|
||||||
|
RawPath: r.URL.RawPath,
|
||||||
|
RawQuery: r.URL.RawQuery,
|
||||||
|
Fragment: r.URL.Fragment,
|
||||||
|
RawFragment: r.URL.RawFragment,
|
||||||
|
},
|
||||||
|
RawURL: r.URL.String(),
|
||||||
Proto: r.Proto,
|
Proto: r.Proto,
|
||||||
ProtoMajor: r.ProtoMajor,
|
ProtoMajor: r.ProtoMajor,
|
||||||
ProtoMinor: r.ProtoMinor,
|
ProtoMinor: r.ProtoMinor,
|
||||||
@ -65,6 +97,18 @@ func (l *Layer) applyRequestRules(r *http.Request, options *LayerOptions) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Layer) getRequestRuleEngine(r *http.Request, options *LayerOptions) (*rule.Engine[*RequestEnv], error) {
|
||||||
|
engine, err := rule.NewEngine[*RequestEnv](
|
||||||
|
rule.WithRules(options.Rules.Request...),
|
||||||
|
ruleHTTP.WithRequestFuncs(r),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
|
||||||
type ResponseEnv struct {
|
type ResponseEnv struct {
|
||||||
Request RequestInfo `expr:"request"`
|
Request RequestInfo `expr:"request"`
|
||||||
Response ResponseInfo `expr:"response"`
|
Response ResponseInfo `expr:"response"`
|
||||||
@ -84,23 +128,37 @@ type ResponseInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layer) applyResponseRules(r *http.Response, options *LayerOptions) error {
|
func (l *Layer) applyResponseRules(r *http.Response, options *LayerOptions) error {
|
||||||
rules := options.Rules.Request
|
rules := options.Rules.Response
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := rule.NewEngine[*ResponseEnv](
|
engine, err := l.getResponseRuleEngine(r, options)
|
||||||
rule.WithRules(options.Rules.Response...),
|
|
||||||
ruleHTTP.WithResponseFuncs(r),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := &ResponseEnv{
|
env := &ResponseEnv{
|
||||||
Request: RequestInfo{
|
Request: RequestInfo{
|
||||||
Method: r.Request.Method,
|
Method: r.Request.Method,
|
||||||
URL: r.Request.URL.String(),
|
URL: URLEnv{
|
||||||
|
Scheme: r.Request.URL.Scheme,
|
||||||
|
Opaque: r.Request.URL.Opaque,
|
||||||
|
User: UserInfoEnv{
|
||||||
|
Username: r.Request.URL.User.Username(),
|
||||||
|
Password: func() string {
|
||||||
|
passwd, _ := r.Request.URL.User.Password()
|
||||||
|
return passwd
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
Host: r.Request.URL.Host,
|
||||||
|
Path: r.Request.URL.Path,
|
||||||
|
RawPath: r.Request.URL.RawPath,
|
||||||
|
RawQuery: r.Request.URL.RawQuery,
|
||||||
|
Fragment: r.Request.URL.Fragment,
|
||||||
|
RawFragment: r.Request.URL.RawFragment,
|
||||||
|
},
|
||||||
|
RawURL: r.Request.URL.String(),
|
||||||
Proto: r.Request.Proto,
|
Proto: r.Request.Proto,
|
||||||
ProtoMajor: r.Request.ProtoMajor,
|
ProtoMajor: r.Request.ProtoMajor,
|
||||||
ProtoMinor: r.Request.ProtoMinor,
|
ProtoMinor: r.Request.ProtoMinor,
|
||||||
@ -131,3 +189,15 @@ func (l *Layer) applyResponseRules(r *http.Response, options *LayerOptions) erro
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Layer) getResponseRuleEngine(r *http.Response, options *LayerOptions) (*rule.Engine[*ResponseEnv], error) {
|
||||||
|
engine, err := rule.NewEngine[*ResponseEnv](
|
||||||
|
rule.WithRules(options.Rules.Response...),
|
||||||
|
ruleHTTP.WithResponseFuncs(r),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
@ -31,8 +31,8 @@ func BenchmarkProxy(b *testing.B) {
|
|||||||
Addrs: []string{redisEndpoint},
|
Addrs: []string{redisEndpoint},
|
||||||
})
|
})
|
||||||
|
|
||||||
proxyRepository := redisStore.NewProxyRepository(client)
|
proxyRepository := redisStore.NewProxyRepository(client, redisStore.DefaultTxMaxAttempts, redisStore.DefaultTxBaseDelay)
|
||||||
layerRepository := redisStore.NewLayerRepository(client)
|
layerRepository := redisStore.NewLayerRepository(client, redisStore.DefaultTxMaxAttempts, redisStore.DefaultTxBaseDelay)
|
||||||
|
|
||||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
@ -17,9 +17,9 @@ func NewRedisClient(ctx context.Context, conf config.RedisConfig) redis.Universa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyRepository(ctx context.Context, client redis.UniversalClient) (store.ProxyRepository, error) {
|
func NewProxyRepository(ctx context.Context, client redis.UniversalClient) (store.ProxyRepository, error) {
|
||||||
return redisStore.NewProxyRepository(client), nil
|
return redisStore.NewProxyRepository(client, redisStore.DefaultTxMaxAttempts, redisStore.DefaultTxBaseDelay), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLayerRepository(ctx context.Context, client redis.UniversalClient) (store.LayerRepository, error) {
|
func NewLayerRepository(ctx context.Context, client redis.UniversalClient) (store.LayerRepository, error) {
|
||||||
return redisStore.NewLayerRepository(client), nil
|
return redisStore.NewLayerRepository(client, redisStore.DefaultTxMaxAttempts, redisStore.DefaultTxBaseDelay), nil
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,10 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LayerHeader struct {
|
type LayerHeader struct {
|
||||||
Proxy ProxyName `json:"proxy"`
|
Proxy ProxyName `json:"proxy"`
|
||||||
Name LayerName `json:"name"`
|
Name LayerName `json:"name"`
|
||||||
Type LayerType `json:"type"`
|
Revision int `json:"revision"`
|
||||||
|
Type LayerType `json:"type"`
|
||||||
|
|
||||||
Weight int `json:"weight"`
|
Weight int `json:"weight"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
type ProxyName Name
|
type ProxyName Name
|
||||||
|
|
||||||
type ProxyHeader struct {
|
type ProxyHeader struct {
|
||||||
Name ProxyName `json:"name"`
|
Name ProxyName `json:"name"`
|
||||||
|
Revision int `json:"revision"`
|
||||||
Weight int `json:"weight"`
|
Weight int `json:"weight"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
|
@ -3,10 +3,18 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultTxMaxAttempts = 20
|
||||||
|
DefaultTxBaseDelay = 100 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
type jsonWrapper[T any] struct {
|
type jsonWrapper[T any] struct {
|
||||||
@ -65,6 +73,33 @@ func key(parts ...string) string {
|
|||||||
return strings.Join(parts, ":")
|
return strings.Join(parts, ":")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithRetry(ctx context.Context, client redis.UniversalClient, key string, fn func(ctx context.Context, tx *redis.Tx) error, maxAttempts int, baseDelay time.Duration) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
delay := baseDelay
|
||||||
|
|
||||||
|
for attempt := 0; attempt < maxAttempts; attempt++ {
|
||||||
|
if err = WithTx(ctx, client, key, fn); err != nil {
|
||||||
|
err = errors.WithStack(err)
|
||||||
|
logger.Debug(ctx, "redis transaction failed", logger.E(err))
|
||||||
|
|
||||||
|
if errors.Is(err, redis.TxFailedErr) {
|
||||||
|
logger.Debug(ctx, "retrying redis transaction", logger.F("attempts", attempt), logger.F("delay", delay))
|
||||||
|
time.Sleep(delay)
|
||||||
|
delay = delay*2 + time.Duration(rand.Int63n(int64(baseDelay)))
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.WithStack(redis.TxFailedErr)
|
||||||
|
}
|
||||||
|
|
||||||
func WithTx(ctx context.Context, client redis.UniversalClient, key string, fn func(ctx context.Context, tx *redis.Tx) error) error {
|
func WithTx(ctx context.Context, client redis.UniversalClient, key string, fn func(ctx context.Context, tx *redis.Tx) error) error {
|
||||||
txf := func(tx *redis.Tx) error {
|
txf := func(tx *redis.Tx) error {
|
||||||
if err := fn(ctx, tx); err != nil {
|
if err := fn(ctx, tx); err != nil {
|
||||||
|
@ -8,9 +8,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type layerHeaderItem struct {
|
type layerHeaderItem struct {
|
||||||
Proxy string `redis:"proxy"`
|
Proxy string `redis:"proxy"`
|
||||||
Name string `redis:"name"`
|
Name string `redis:"name"`
|
||||||
Type string `redis:"type"`
|
Revision int `redis:"revision"`
|
||||||
|
Type string `redis:"type"`
|
||||||
|
|
||||||
Weight int `redis:"weight"`
|
Weight int `redis:"weight"`
|
||||||
Enabled bool `redis:"enabled"`
|
Enabled bool `redis:"enabled"`
|
||||||
@ -18,11 +19,12 @@ type layerHeaderItem struct {
|
|||||||
|
|
||||||
func (i *layerHeaderItem) ToLayerHeader() (*store.LayerHeader, error) {
|
func (i *layerHeaderItem) ToLayerHeader() (*store.LayerHeader, error) {
|
||||||
layerHeader := &store.LayerHeader{
|
layerHeader := &store.LayerHeader{
|
||||||
Proxy: store.ProxyName(i.Proxy),
|
Proxy: store.ProxyName(i.Proxy),
|
||||||
Name: store.LayerName(i.Name),
|
Name: store.LayerName(i.Name),
|
||||||
Type: store.LayerType(i.Type),
|
Revision: i.Revision,
|
||||||
Weight: i.Weight,
|
Type: store.LayerType(i.Type),
|
||||||
Enabled: i.Enabled,
|
Weight: i.Weight,
|
||||||
|
Enabled: i.Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
return layerHeader, nil
|
return layerHeader, nil
|
||||||
|
@ -14,7 +14,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LayerRepository struct {
|
type LayerRepository struct {
|
||||||
client redis.UniversalClient
|
client redis.UniversalClient
|
||||||
|
txMaxAttempts int
|
||||||
|
txRetryBaseDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLayer implements store.LayerRepository
|
// CreateLayer implements store.LayerRepository
|
||||||
@ -24,11 +26,12 @@ func (r *LayerRepository) CreateLayer(ctx context.Context, proxyName store.Proxy
|
|||||||
|
|
||||||
layerItem := &layerItem{
|
layerItem := &layerItem{
|
||||||
layerHeaderItem: layerHeaderItem{
|
layerHeaderItem: layerHeaderItem{
|
||||||
Proxy: string(proxyName),
|
Proxy: string(proxyName),
|
||||||
Name: string(layerName),
|
Name: string(layerName),
|
||||||
Type: string(layerType),
|
Type: string(layerType),
|
||||||
Weight: 0,
|
Weight: 0,
|
||||||
Enabled: false,
|
Revision: 0,
|
||||||
|
Enabled: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
CreatedAt: wrap(now),
|
CreatedAt: wrap(now),
|
||||||
@ -96,7 +99,7 @@ func (r *LayerRepository) GetLayer(ctx context.Context, proxyName store.ProxyNam
|
|||||||
key := layerKey(proxyName, layerName)
|
key := layerKey(proxyName, layerName)
|
||||||
var layerItem *layerItem
|
var layerItem *layerItem
|
||||||
|
|
||||||
err := WithTx(ctx, r.client, key, func(ctx context.Context, tx *redis.Tx) error {
|
err := WithRetry(ctx, r.client, key, func(ctx context.Context, tx *redis.Tx) error {
|
||||||
pItem, err := r.txGetLayerItem(ctx, tx, proxyName, layerName)
|
pItem, err := r.txGetLayerItem(ctx, tx, proxyName, layerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@ -105,7 +108,7 @@ func (r *LayerRepository) GetLayer(ctx context.Context, proxyName store.ProxyNam
|
|||||||
layerItem = pItem
|
layerItem = pItem
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}, r.txMaxAttempts, r.txRetryBaseDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -197,7 +200,7 @@ func (r *LayerRepository) UpdateLayer(ctx context.Context, proxyName store.Proxy
|
|||||||
key := layerKey(proxyName, layerName)
|
key := layerKey(proxyName, layerName)
|
||||||
var layerItem layerItem
|
var layerItem layerItem
|
||||||
|
|
||||||
err := WithTx(ctx, r.client, key, func(ctx context.Context, tx *redis.Tx) error {
|
err := WithRetry(ctx, r.client, key, func(ctx context.Context, tx *redis.Tx) error {
|
||||||
item, err := r.txGetLayerItem(ctx, tx, proxyName, layerName)
|
item, err := r.txGetLayerItem(ctx, tx, proxyName, layerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@ -216,6 +219,7 @@ func (r *LayerRepository) UpdateLayer(ctx context.Context, proxyName store.Proxy
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.UpdatedAt = wrap(time.Now().UTC())
|
item.UpdatedAt = wrap(time.Now().UTC())
|
||||||
|
item.Revision = item.Revision + 1
|
||||||
|
|
||||||
_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
|
_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
|
||||||
p.HMSet(ctx, key, item.layerHeaderItem)
|
p.HMSet(ctx, key, item.layerHeaderItem)
|
||||||
@ -230,7 +234,7 @@ func (r *LayerRepository) UpdateLayer(ctx context.Context, proxyName store.Proxy
|
|||||||
layerItem = *item
|
layerItem = *item
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}, r.txMaxAttempts, r.txRetryBaseDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -243,9 +247,11 @@ func (r *LayerRepository) UpdateLayer(ctx context.Context, proxyName store.Proxy
|
|||||||
return layer, nil
|
return layer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLayerRepository(client redis.UniversalClient) *LayerRepository {
|
func NewLayerRepository(client redis.UniversalClient, txMaxAttempts int, txRetryBaseDelay time.Duration) *LayerRepository {
|
||||||
return &LayerRepository{
|
return &LayerRepository{
|
||||||
client: client,
|
client: client,
|
||||||
|
txMaxAttempts: txMaxAttempts,
|
||||||
|
txRetryBaseDelay: txRetryBaseDelay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestLayerRepository(t *testing.T) {
|
func TestLayerRepository(t *testing.T) {
|
||||||
repository := NewLayerRepository(client)
|
repository := NewLayerRepository(client, DefaultTxMaxAttempts, DefaultTxBaseDelay)
|
||||||
testsuite.TestLayerRepository(t, repository)
|
testsuite.TestLayerRepository(t, repository)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type proxyHeaderItem struct {
|
type proxyHeaderItem struct {
|
||||||
Name string `redis:"name"`
|
Name string `redis:"name"`
|
||||||
|
Revision int `redis:"revision"`
|
||||||
|
|
||||||
Weight int `redis:"weight"`
|
Weight int `redis:"weight"`
|
||||||
Enabled bool `redis:"enabled"`
|
Enabled bool `redis:"enabled"`
|
||||||
@ -19,9 +20,10 @@ type proxyHeaderItem struct {
|
|||||||
|
|
||||||
func (i *proxyHeaderItem) ToProxyHeader() (*store.ProxyHeader, error) {
|
func (i *proxyHeaderItem) ToProxyHeader() (*store.ProxyHeader, error) {
|
||||||
proxyHeader := &store.ProxyHeader{
|
proxyHeader := &store.ProxyHeader{
|
||||||
Name: store.ProxyName(i.Name),
|
Name: store.ProxyName(i.Name),
|
||||||
Weight: i.Weight,
|
Revision: i.Revision,
|
||||||
Enabled: i.Enabled,
|
Weight: i.Weight,
|
||||||
|
Enabled: i.Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxyHeader, nil
|
return proxyHeader, nil
|
||||||
|
@ -14,7 +14,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ProxyRepository struct {
|
type ProxyRepository struct {
|
||||||
client redis.UniversalClient
|
client redis.UniversalClient
|
||||||
|
txMaxAttempts int
|
||||||
|
txRetryBaseDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProxy implements store.ProxyRepository
|
// GetProxy implements store.ProxyRepository
|
||||||
@ -22,7 +24,7 @@ func (r *ProxyRepository) GetProxy(ctx context.Context, name store.ProxyName) (*
|
|||||||
key := proxyKey(name)
|
key := proxyKey(name)
|
||||||
var proxyItem *proxyItem
|
var proxyItem *proxyItem
|
||||||
|
|
||||||
err := WithTx(ctx, r.client, key, func(ctx context.Context, tx *redis.Tx) error {
|
err := WithRetry(ctx, r.client, key, func(ctx context.Context, tx *redis.Tx) error {
|
||||||
pItem, err := r.txGetProxyItem(ctx, tx, name)
|
pItem, err := r.txGetProxyItem(ctx, tx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@ -31,7 +33,7 @@ func (r *ProxyRepository) GetProxy(ctx context.Context, name store.ProxyName) (*
|
|||||||
proxyItem = pItem
|
proxyItem = pItem
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}, r.txMaxAttempts, r.txRetryBaseDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -89,6 +91,7 @@ func (r *ProxyRepository) CreateProxy(ctx context.Context, name store.ProxyName,
|
|||||||
CreatedAt: wrap(now),
|
CreatedAt: wrap(now),
|
||||||
UpdatedAt: wrap(now),
|
UpdatedAt: wrap(now),
|
||||||
Weight: 0,
|
Weight: 0,
|
||||||
|
Revision: 0,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
To: to,
|
To: to,
|
||||||
@ -191,7 +194,7 @@ func (r *ProxyRepository) UpdateProxy(ctx context.Context, name store.ProxyName,
|
|||||||
key := proxyKey(name)
|
key := proxyKey(name)
|
||||||
var proxyItem proxyItem
|
var proxyItem proxyItem
|
||||||
|
|
||||||
err := WithTx(ctx, r.client, key, func(ctx context.Context, tx *redis.Tx) error {
|
err := WithRetry(ctx, r.client, key, func(ctx context.Context, tx *redis.Tx) error {
|
||||||
item, err := r.txGetProxyItem(ctx, tx, name)
|
item, err := r.txGetProxyItem(ctx, tx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@ -214,6 +217,7 @@ func (r *ProxyRepository) UpdateProxy(ctx context.Context, name store.ProxyName,
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.UpdatedAt = wrap(time.Now().UTC())
|
item.UpdatedAt = wrap(time.Now().UTC())
|
||||||
|
item.Revision = item.Revision + 1
|
||||||
|
|
||||||
_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
|
_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
|
||||||
p.HMSet(ctx, key, item.proxyHeaderItem)
|
p.HMSet(ctx, key, item.proxyHeaderItem)
|
||||||
@ -228,7 +232,7 @@ func (r *ProxyRepository) UpdateProxy(ctx context.Context, name store.ProxyName,
|
|||||||
proxyItem = *item
|
proxyItem = *item
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}, r.txMaxAttempts, r.txRetryBaseDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -241,9 +245,11 @@ func (r *ProxyRepository) UpdateProxy(ctx context.Context, name store.ProxyName,
|
|||||||
return proxy, nil
|
return proxy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyRepository(client redis.UniversalClient) *ProxyRepository {
|
func NewProxyRepository(client redis.UniversalClient, txMaxAttempts int, txRetryBaseDelay time.Duration) *ProxyRepository {
|
||||||
return &ProxyRepository{
|
return &ProxyRepository{
|
||||||
client: client,
|
client: client,
|
||||||
|
txMaxAttempts: 20,
|
||||||
|
txRetryBaseDelay: txRetryBaseDelay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestProxyRepository(t *testing.T) {
|
func TestProxyRepository(t *testing.T) {
|
||||||
repository := NewProxyRepository(client)
|
repository := NewProxyRepository(client, DefaultTxMaxAttempts, DefaultTxBaseDelay)
|
||||||
testsuite.TestProxyRepository(t, repository)
|
testsuite.TestProxyRepository(t, repository)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package testsuite
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
@ -49,6 +50,10 @@ var layerRepositoryTestCases = []layerRepositoryTestCase{
|
|||||||
return errors.Errorf("layer.UpdatedAt should not be zero value")
|
return errors.Errorf("layer.UpdatedAt should not be zero value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if layer.Revision != 0 {
|
||||||
|
return errors.Errorf("layer.Revision should be zero")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -230,6 +235,86 @@ var layerRepositoryTestCases = []layerRepositoryTestCase{
|
|||||||
return errors.New("could not find created layer in query results")
|
return errors.New("could not find created layer in query results")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Create then update layer",
|
||||||
|
Do: func(repo store.LayerRepository) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var layerName store.LayerName = "create_then_update_layer"
|
||||||
|
var proxyName store.ProxyName = store.ProxyName(string(layerName) + "_proxy")
|
||||||
|
var layerType store.LayerType = "dummy"
|
||||||
|
var layerOptions store.LayerOptions = store.LayerOptions{}
|
||||||
|
|
||||||
|
createdLayer, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := 0, createdLayer.Revision; e != g {
|
||||||
|
return errors.Errorf("createdLayer.Revision: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedLayer, err := repo.UpdateLayer(ctx, proxyName, layerName)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "err should be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := 1, updatedLayer.Revision; e != g {
|
||||||
|
return errors.Errorf("updatedLayer.Revision: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Update layer concurrently",
|
||||||
|
Do: func(repo store.LayerRepository) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var layerName store.LayerName = "update_layer_concurrently"
|
||||||
|
var proxyName store.ProxyName = store.ProxyName(string(layerName) + "_proxy")
|
||||||
|
var layerType store.LayerType = "dummy"
|
||||||
|
var layerOptions store.LayerOptions = store.LayerOptions{}
|
||||||
|
|
||||||
|
createdLayer, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdLayer.Revision != 0 {
|
||||||
|
return errors.Errorf("createdLayer.Revision should be zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
total := 100
|
||||||
|
|
||||||
|
wg.Add(total)
|
||||||
|
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if _, err := repo.UpdateLayer(ctx, createdLayer.Proxy, createdLayer.Name); err != nil {
|
||||||
|
panic(errors.Wrap(err, "err should be nil"))
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
layer, err := repo.GetLayer(ctx, createdLayer.Proxy, createdLayer.Name)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "err should be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := total, layer.Revision; e != g {
|
||||||
|
return errors.Errorf("layer.Revision: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ package testsuite
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
@ -51,6 +52,10 @@ var proxyRepositoryTestCases = []proxyRepositoryTestCase{
|
|||||||
return errors.Errorf("proxy.UpdatedAt should not be zero value")
|
return errors.Errorf("proxy.UpdatedAt should not be zero value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if proxy.Revision != 0 {
|
||||||
|
return errors.Errorf("proxy.Revision should be zero")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -99,6 +104,10 @@ var proxyRepositoryTestCases = []proxyRepositoryTestCase{
|
|||||||
return errors.Errorf("foundProxy.UpdatedAt: expected '%v', got '%v'", createdProxy.UpdatedAt, foundProxy.UpdatedAt)
|
return errors.Errorf("foundProxy.UpdatedAt: expected '%v', got '%v'", createdProxy.UpdatedAt, foundProxy.UpdatedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if foundProxy.Revision != 0 {
|
||||||
|
return errors.Errorf("foundProxy.Revision should be zero")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -194,6 +203,84 @@ var proxyRepositoryTestCases = []proxyRepositoryTestCase{
|
|||||||
return errors.Errorf("err: expected store.ErrAlreadyExists, got '%+v'", err)
|
return errors.Errorf("err: expected store.ErrAlreadyExists, got '%+v'", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Create then update proxy",
|
||||||
|
Do: func(repo store.ProxyRepository) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
to := "http://example.com"
|
||||||
|
|
||||||
|
var name store.ProxyName = "create_then_update_proxy"
|
||||||
|
|
||||||
|
createdProxy, err := repo.CreateProxy(ctx, name, to, "127.0.0.1:*", "localhost:*")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "err should be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdProxy.Revision != 0 {
|
||||||
|
return errors.Errorf("createdProxy.Revision should be zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedProxy, err := repo.UpdateProxy(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "err should be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := 1, updatedProxy.Revision; e != g {
|
||||||
|
return errors.Errorf("updatedProxy.Revision: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Update proxy concurrently",
|
||||||
|
Do: func(repo store.ProxyRepository) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
to := "http://example.com"
|
||||||
|
|
||||||
|
var name store.ProxyName = "update_proxy_concurrently"
|
||||||
|
|
||||||
|
createdProxy, err := repo.CreateProxy(ctx, name, to, "127.0.0.1:*", "localhost:*")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "err should be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdProxy.Revision != 0 {
|
||||||
|
return errors.Errorf("createdProxy.Revision should be zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
total := 100
|
||||||
|
|
||||||
|
wg.Add(total)
|
||||||
|
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if _, err := repo.UpdateProxy(ctx, name); err != nil {
|
||||||
|
panic(errors.Wrap(err, "err should be nil"))
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
proxy, err := repo.GetProxy(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "err should be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := total, proxy.Revision; e != g {
|
||||||
|
return errors.Errorf("proxy.Revision: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user