Skip to content

Commit

Permalink
Nested policy configuration (#6)
Browse files Browse the repository at this point in the history
Revert flat policy configuration params.
Fix README.md typos with configuration examples
---------

Signed-off-by: Gleb Kogtev <gleb.kogtev@gmail.com>
  • Loading branch information
glebkin authored Sep 2, 2024
1 parent 3d1ffcb commit 48958d5
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 75 deletions.
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
}
}
~~~
Expand All @@ -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
}
}
~~~
2 changes: 0 additions & 2 deletions fanout.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
102 changes: 56 additions & 46 deletions setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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":
Expand All @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down
35 changes: 16 additions & 19 deletions setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 48958d5

Please sign in to comment.