From 48958d5cc5ec859c3807a05fe245304f63e75d52 Mon Sep 17 00:00:00 2001 From: Gleb Kogtev Date: Mon, 2 Sep 2024 17:52:59 +0300 Subject: [PATCH] Nested policy configuration (#6) Revert flat policy configuration params. Fix README.md typos with configuration examples --------- Signed-off-by: Gleb Kogtev --- README.md | 17 +++++---- fanout.go | 2 - setup.go | 102 +++++++++++++++++++++++++++----------------------- setup_test.go | 35 ++++++++--------- 4 files changed, 81 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index ef0e8a6..9ae1463 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ Each incoming DNS query that hits the CoreDNS fanout plugin will be replicated i * `worker-count` is the number of parallel queries per request. By default equals to count of IP list. Use this only for reducing parallel queries per request. * `policy` - specifies the policy of DNS server selection mechanism. The default is `sequential`. * `sequential` - select DNS servers one-by-one based on its order - * `weighted-random` - select DNS servers randomly based on `weighted-random-server-count` and `weighted-random-load-factor` params. -* `weighted-random-server-count` is the number of DNS servers to be requested. Equals to the number of specified IPs by default. Used only with the `weighted-random` policy. -* `weighted-random-load-factor` - the probability of selecting a server. This is specified in the order of the list of IP addresses and takes values between 1 and 100. By default, all servers have an equal probability of 100. Used only with the `weighted-random` policy. + * `weighted-random` - select DNS servers randomly based on `server-count` and `load-factor` params: + * `server-count` is the number of DNS servers to be requested. Equals to the number of specified IPs by default. + * `load-factor` - the probability of selecting a server. This is specified in the order of the list of IP addresses and takes values between 1 and 100. By default, all servers have an equal probability of 100. * `network` is a specific network protocol. Could be `tcp`, `udp`, `tcp-tls`. * `except` is a list is a space-separated list of domains to exclude from proxying. * `except-file` is the path to file with line-separated list of domains to exclude from proxying. @@ -116,13 +116,14 @@ If `race` is enable, we will get `NXDOMAIN` result quickly, otherwise we will ge } ~~~ -Sends parallel requests between two randomly selected resolvers. Note, that `127.0.0.1:9007` would be selected more frequently as it has the highest `weighted-random-load-factor`. +Sends parallel requests between two randomly selected resolvers. Note, that `127.0.0.1:9007` would be selected more frequently as it has the highest `load-factor`. ~~~ corefile example.org { fanout . 127.0.0.1:9005 127.0.0.1:9006 127.0.0.1:9007 { - policy weighted-random - weighted-random-server-count 2 - weighted-random-load-factor 50 70 100 + policy weighted-random { + server-count 2 + load-factor 50 70 100 + } } } ~~~ @@ -131,7 +132,7 @@ Sends parallel requests between three resolver sequentially (default mode). ~~~ corefile example.org { fanout . 127.0.0.1:9005 127.0.0.1:9006 127.0.0.1:9007 { - policy sequential + policy sequential } } ~~~ diff --git a/fanout.go b/fanout.go index 60eb3aa..4e3dc88 100644 --- a/fanout.go +++ b/fanout.go @@ -49,8 +49,6 @@ type Fanout struct { attempts int workerCount int serverCount int - loadFactor []int - policyType string serverSelectionPolicy policy tapPlugin *dnstap.Dnstap Next plugin.Handler diff --git a/setup.go b/setup.go index f5ab666..69f4977 100644 --- a/setup.go +++ b/setup.go @@ -122,15 +122,14 @@ func parsefanoutStanza(c *caddyfile.Dispenser) (*Fanout, error) { return f, err } for c.NextBlock() { - err = parseValue(strings.ToLower(c.Val()), f, c) + err = parseValue(strings.ToLower(c.Val()), f, c, toHosts) if err != nil { return nil, err } } initClients(f, toHosts) - err = initServerSelectionPolicy(f) - if err != nil { - return nil, err + if f.serverCount > len(f.clients) || f.serverCount == 0 { + f.serverCount = len(f.clients) } if f.workerCount > len(f.clients) || f.workerCount == 0 { @@ -156,30 +155,7 @@ func initClients(f *Fanout, hosts []string) { } } -func initServerSelectionPolicy(f *Fanout) error { - if f.serverCount > len(f.clients) || f.serverCount == 0 { - f.serverCount = len(f.clients) - } - - loadFactor := f.loadFactor - if len(loadFactor) == 0 { - for i := 0; i < len(f.clients); i++ { - loadFactor = append(loadFactor, maxLoadFactor) - } - } - if len(loadFactor) != len(f.clients) { - return errors.New("load-factor params count must be the same as the number of hosts") - } - - f.serverSelectionPolicy = &sequentialPolicy{} - if f.policyType == policyWeightedRandom { - f.serverSelectionPolicy = &weightedPolicy{loadFactor: loadFactor} - } - - return nil -} - -func parseValue(v string, f *Fanout, c *caddyfile.Dispenser) error { +func parseValue(v string, f *Fanout, c *caddyfile.Dispenser, hosts []string) error { switch v { case "tls": return parseTLS(f, c) @@ -190,13 +166,7 @@ func parseValue(v string, f *Fanout, c *caddyfile.Dispenser) error { case "worker-count": return parseWorkerCount(f, c) case "policy": - return parsePolicy(f, c) - case "weighted-random-server-count": - serverCount, err := parsePositiveInt(c) - f.serverCount = serverCount - return err - case "weighted-random-load-factor": - return parseLoadFactor(f, c) + return parsePolicy(f, c, hosts) case "timeout": return parseTimeout(f, c) case "race": @@ -214,16 +184,55 @@ func parseValue(v string, f *Fanout, c *caddyfile.Dispenser) error { } } -func parsePolicy(f *Fanout, c *caddyfile.Dispenser) error { +func parsePolicy(f *Fanout, c *caddyfile.Dispenser, hosts []string) error { if !c.NextArg() { return c.ArgErr() } - policyType := strings.ToLower(c.Val()) - if policyType != policyWeightedRandom && policyType != policySequential { + switch c.Val() { + case policyWeightedRandom: + // omit "{" + c.Next() + if c.Val() != "{" { + return c.Err("Wrong policy configuration") + } + case policySequential: + f.serverSelectionPolicy = &sequentialPolicy{} + return nil + default: return errors.Errorf("unknown policy %q", c.Val()) } - f.policyType = policyType + + var loadFactor []int + for c.Next() { + if c.Val() == "}" { + break + } + + var err error + switch c.Val() { + case "server-count": + f.serverCount, err = parsePositiveInt(c) + case "load-factor": + loadFactor, err = parseLoadFactor(c) + default: + return errors.Errorf("unknown property %q", c.Val()) + } + if err != nil { + return err + } + } + + if len(loadFactor) == 0 { + for i := 0; i < len(hosts); i++ { + loadFactor = append(loadFactor, maxLoadFactor) + } + } + if len(loadFactor) != len(hosts) { + return errors.New("load-factor params count must be the same as the number of hosts") + } + + f.serverSelectionPolicy = &weightedPolicy{loadFactor: loadFactor} return nil } @@ -295,29 +304,30 @@ func parseWorkerCount(f *Fanout, c *caddyfile.Dispenser) error { return err } -func parseLoadFactor(f *Fanout, c *caddyfile.Dispenser) error { +func parseLoadFactor(c *caddyfile.Dispenser) ([]int, error) { args := c.RemainingArgs() if len(args) == 0 { - return c.ArgErr() + return nil, c.ArgErr() } + result := make([]int, 0, len(args)) for _, arg := range args { loadFactor, err := strconv.Atoi(arg) if err != nil { - return c.ArgErr() + return nil, c.ArgErr() } if loadFactor < minLoadFactor { - return errors.New("load-factor should be more or equal 1") + return nil, errors.New("load-factor should be more or equal 1") } if loadFactor > maxLoadFactor { - return errors.Errorf("load-factor %d should be less than %d", loadFactor, maxLoadFactor) + return nil, errors.Errorf("load-factor %d should be less than %d", loadFactor, maxLoadFactor) } - f.loadFactor = append(f.loadFactor, loadFactor) + result = append(result, loadFactor) } - return nil + return result, nil } func parsePositiveInt(c *caddyfile.Dispenser) (int, error) { diff --git a/setup_test.go b/setup_test.go index d636f1c..c8fc793 100644 --- a/setup_test.go +++ b/setup_test.go @@ -40,19 +40,18 @@ func TestSetup(t *testing.T) { expectedNetwork string expectedServerCount int expectedLoadFactor []int - expectedPolicy string expectedErr string }{ // positive - {input: "fanout . 127.0.0.1 {\npolicy weighted-random \nweighted-random-server-count 5 weighted-random-load-factor 100\n}", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 1, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 1, expectedLoadFactor: []int{100}, expectedPolicy: policyWeightedRandom}, - {input: "fanout . 127.0.0.1", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 1, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 1, expectedLoadFactor: nil, expectedPolicy: ""}, - {input: "fanout . 127.0.0.1 {\npolicy weighted-random \nserver-count 5 load-factor 100\n}", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 1, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 1, expectedLoadFactor: []int{100}, expectedPolicy: policyWeightedRandom}, - {input: "fanout . 127.0.0.1 {\nexcept a b\nworker-count 3\n}", expectedFrom: ".", expectedTimeout: defaultTimeout, expectedAttempts: 3, expectedWorkers: 1, expectedIgnored: []string{"a.", "b."}, expectedNetwork: "udp", expectedServerCount: 1, expectedLoadFactor: nil, expectedPolicy: ""}, - {input: "fanout . 127.0.0.1 127.0.0.2 {\nnetwork tcp\n}", expectedFrom: ".", expectedTimeout: defaultTimeout, expectedAttempts: 3, expectedWorkers: 2, expectedNetwork: "tcp", expectedTo: []string{"127.0.0.1:53", "127.0.0.2:53"}, expectedServerCount: 2, expectedLoadFactor: nil, expectedPolicy: ""}, - {input: "fanout . 127.0.0.1 127.0.0.2 127.0.0.3 127.0.0.4 {\nworker-count 3\ntimeout 1m\n}", expectedTimeout: time.Minute, expectedAttempts: 3, expectedFrom: ".", expectedWorkers: 3, expectedNetwork: "udp", expectedServerCount: 4, expectedLoadFactor: nil, expectedPolicy: ""}, - {input: "fanout . 127.0.0.1 127.0.0.2 127.0.0.3 127.0.0.4 {\nattempt-count 2\n}", expectedTimeout: defaultTimeout, expectedFrom: ".", expectedAttempts: 2, expectedWorkers: 4, expectedNetwork: "udp", expectedServerCount: 4, expectedLoadFactor: nil, expectedPolicy: ""}, - {input: "fanout . 127.0.0.1 127.0.0.2 127.0.0.3 {\npolicy weighted-random \n}", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 3, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 3, expectedLoadFactor: []int{100, 100, 100}, expectedPolicy: policyWeightedRandom}, - {input: "fanout . 127.0.0.1 127.0.0.2 127.0.0.3 {\npolicy sequential\nworker-count 3\n}", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 3, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 3, expectedLoadFactor: nil, expectedPolicy: policySequential}, + {input: "fanout . 127.0.0.1 {\npolicy weighted-random {\nserver-count 5 load-factor 100\n}", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 1, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 1, expectedLoadFactor: []int{100}}, + {input: "fanout . 127.0.0.1", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 1, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 1, expectedLoadFactor: nil}, + {input: "fanout . 127.0.0.1 {\npolicy weighted-random {\nserver-count 5 load-factor 100\n}", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 1, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 1, expectedLoadFactor: []int{100}}, + {input: "fanout . 127.0.0.1 {\nexcept a b\nworker-count 3\n}", expectedFrom: ".", expectedTimeout: defaultTimeout, expectedAttempts: 3, expectedWorkers: 1, expectedIgnored: []string{"a.", "b."}, expectedNetwork: "udp", expectedServerCount: 1, expectedLoadFactor: nil}, + {input: "fanout . 127.0.0.1 127.0.0.2 {\nnetwork tcp\n}", expectedFrom: ".", expectedTimeout: defaultTimeout, expectedAttempts: 3, expectedWorkers: 2, expectedNetwork: "tcp", expectedTo: []string{"127.0.0.1:53", "127.0.0.2:53"}, expectedServerCount: 2, expectedLoadFactor: nil}, + {input: "fanout . 127.0.0.1 127.0.0.2 127.0.0.3 127.0.0.4 {\nworker-count 3\ntimeout 1m\n}", expectedTimeout: time.Minute, expectedAttempts: 3, expectedFrom: ".", expectedWorkers: 3, expectedNetwork: "udp", expectedServerCount: 4, expectedLoadFactor: nil}, + {input: "fanout . 127.0.0.1 127.0.0.2 127.0.0.3 127.0.0.4 {\nattempt-count 2\n}", expectedTimeout: defaultTimeout, expectedFrom: ".", expectedAttempts: 2, expectedWorkers: 4, expectedNetwork: "udp", expectedServerCount: 4, expectedLoadFactor: nil}, + {input: "fanout . 127.0.0.1 127.0.0.2 127.0.0.3 {\npolicy weighted-random {}\n}", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 3, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 3, expectedLoadFactor: []int{100, 100, 100}}, + {input: "fanout . 127.0.0.1 127.0.0.2 127.0.0.3 {\npolicy sequential\nworker-count 3\n}", expectedFrom: ".", expectedAttempts: 3, expectedWorkers: 3, expectedTimeout: defaultTimeout, expectedNetwork: "udp", expectedServerCount: 3, expectedLoadFactor: nil}, // negative {input: "fanout . aaa", expectedErr: "not an IP address or file"}, @@ -61,12 +60,13 @@ func TestSetup(t *testing.T) { {input: "fanout . 127.0.0.1 {\nexcept a b\nworker-count ten\n}", expectedErr: "'ten'"}, {input: "fanout . 127.0.0.1 {\nexcept a:\nworker-count ten\n}", expectedErr: "unable to normalize 'a:'"}, {input: "fanout . 127.0.0.1 127.0.0.2 {\nnetwork XXX\n}", expectedErr: "unknown network protocol"}, - {input: "fanout . 127.0.0.1 {\npolicy weighted-random \nweighted-random-server-count -100\n}", expectedErr: "Wrong argument count or unexpected line ending"}, - {input: "fanout . 127.0.0.1 {\npolicy weighted-random \nweighted-random-load-factor 150\n}", expectedErr: "load-factor 150 should be less than 100"}, - {input: "fanout . 127.0.0.1 {\npolicy weighted-random \nweighted-random-load-factor 0\n}", expectedErr: "load-factor should be more or equal 1"}, - {input: "fanout . 127.0.0.1 {\npolicy weighted-random \nweighted-random-load-factor 50 100\n}", expectedErr: "load-factor params count must be the same as the number of hosts"}, - {input: "fanout . 127.0.0.1 127.0.0.2 {\npolicy weighted-random \nweighted-random-load-factor 50\n}", expectedErr: "load-factor params count must be the same as the number of hosts"}, - {input: "fanout . 127.0.0.1 127.0.0.2 {\npolicy weighted-random \nweighted-random-load-factor \n}", expectedErr: "Wrong argument count or unexpected line ending"}, + {input: "fanout . 127.0.0.1 {\npolicy weighted-random {\nserver-count -100\n}\n}", expectedErr: "Wrong argument count or unexpected line ending"}, + {input: "fanout . 127.0.0.1 {\npolicy weighted-random {\nload-factor 150\n}\n}", expectedErr: "load-factor 150 should be less than 100"}, + {input: "fanout . 127.0.0.1 {\npolicy weighted-random {\nload-factor 0\n}\n}", expectedErr: "load-factor should be more or equal 1"}, + {input: "fanout . 127.0.0.1 {\npolicy weighted-random {\nload-factor 50 100\n}\n}", expectedErr: "load-factor params count must be the same as the number of hosts"}, + {input: "fanout . 127.0.0.1 127.0.0.2 {\npolicy weighted-random {\nload-factor 50\n}\n}", expectedErr: "load-factor params count must be the same as the number of hosts"}, + {input: "fanout . 127.0.0.1 127.0.0.2 {\npolicy weighted-random {\nload-factor \n}\n}", expectedErr: "Wrong argument count or unexpected line ending"}, + {input: "fanout . 127.0.0.1 127.0.0.2 {\npolicy weighted-random\nworker-count 10\n}", expectedErr: "Wrong policy configuration"}, } for i, test := range tests { @@ -116,9 +116,6 @@ func TestSetup(t *testing.T) { if f.serverCount != test.expectedServerCount { t.Fatalf("Test %d: expected: %d, got: %d", i, test.expectedServerCount, f.serverCount) } - if f.policyType != test.expectedPolicy { - t.Fatalf("Test %d: expected: %s, got: %s", i, test.expectedPolicy, f.policyType) - } selectionPolicy, ok := f.serverSelectionPolicy.(*weightedPolicy) if len(test.expectedLoadFactor) > 0 {