Skip to content

Commit 21bd11b

Browse files
authored
Merge pull request #604 from jkh52/proxy-strategies
ProxyStrategies: code cleanup and improve test coverage.
2 parents 58c9290 + 64f8c7c commit 21bd11b

File tree

5 files changed

+184
-33
lines changed

5 files changed

+184
-33
lines changed

cmd/server/app/options/options.go

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package options
1919
import (
2020
"fmt"
2121
"os"
22-
"strings"
2322
"time"
2423

2524
"github.com/google/uuid"
@@ -90,10 +89,10 @@ type ProxyRunOptions struct {
9089

9190
// Proxy strategies used by the server.
9291
// NOTE the order of the strategies matters. e.g., for list
93-
// "destHost,destCIDR", the server will try to find a backend associating
92+
// "destHost,destCIDR,default", the server will try to find a backend associating
9493
// to the destination host first, if not found, it will try to find a
9594
// backend within the destCIDR. if it still can't find any backend,
96-
// it will use the default backend manager to choose a random backend.
95+
// it will choose a random backend.
9796
ProxyStrategies string
9897

9998
// Cipher suites used by the server.
@@ -135,7 +134,7 @@ func (o *ProxyRunOptions) Flags() *pflag.FlagSet {
135134
flags.Float32Var(&o.KubeconfigQPS, "kubeconfig-qps", o.KubeconfigQPS, "Maximum client QPS (proxy server uses this client to authenticate agent tokens).")
136135
flags.IntVar(&o.KubeconfigBurst, "kubeconfig-burst", o.KubeconfigBurst, "Maximum client burst (proxy server uses this client to authenticate agent tokens).")
137136
flags.StringVar(&o.AuthenticationAudience, "authentication-audience", o.AuthenticationAudience, "Expected agent's token authentication audience (used with agent-namespace, agent-service-account, kubeconfig).")
138-
flags.StringVar(&o.ProxyStrategies, "proxy-strategies", o.ProxyStrategies, "The list of proxy strategies used by the server to pick a backend/tunnel, available strategies are: default, destHost.")
137+
flags.StringVar(&o.ProxyStrategies, "proxy-strategies", o.ProxyStrategies, "The list of proxy strategies used by the server to pick an agent/tunnel, available strategies are: default, destHost, defaultRoute.")
139138
flags.StringSliceVar(&o.CipherSuites, "cipher-suites", o.CipherSuites, "The comma separated list of allowed cipher suites. Has no effect on TLS1.3. Empty means allow default list.")
140139

141140
flags.Bool("warn-on-channel-limit", true, "This behavior is now thread safe and always on. This flag will be removed in a future release.")
@@ -292,17 +291,11 @@ func (o *ProxyRunOptions) Validate() error {
292291
}
293292

294293
// validate the proxy strategies
295-
if o.ProxyStrategies != "" {
296-
pss := strings.Split(o.ProxyStrategies, ",")
297-
for _, ps := range pss {
298-
switch ps {
299-
case string(server.ProxyStrategyDestHost):
300-
case string(server.ProxyStrategyDefault):
301-
case string(server.ProxyStrategyDefaultRoute):
302-
default:
303-
return fmt.Errorf("unknown proxy strategy: %s, available strategy are: default, destHost, defaultRoute", ps)
304-
}
305-
}
294+
if len(o.ProxyStrategies) == 0 {
295+
return fmt.Errorf("ProxyStrategies cannot be empty")
296+
}
297+
if _, err := server.ParseProxyStrategies(o.ProxyStrategies); err != nil {
298+
return fmt.Errorf("invalid proxy strategies: %v", err)
306299
}
307300

308301
// validate the cipher suites

cmd/server/app/options/options_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ func TestValidate(t *testing.T) {
145145
value: "TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
146146
expected: nil,
147147
},
148+
"Empty proxy strategies": {
149+
field: "ProxyStrategies",
150+
value: "",
151+
expected: fmt.Errorf("ProxyStrategies cannot be empty"),
152+
},
153+
"Invalid proxy strategies": {
154+
field: "ProxyStrategies",
155+
value: "invalid",
156+
expected: fmt.Errorf("invalid proxy strategies: unknown proxy strategy: invalid"),
157+
},
148158
} {
149159
t.Run(desc, func(t *testing.T) {
150160
testServerOptions := NewProxyRunOptions()

cmd/server/app/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func (p *Proxy) Run(o *options.ProxyRunOptions, stopCh <-chan struct{}) error {
128128
AuthenticationAudience: o.AuthenticationAudience,
129129
}
130130
klog.V(1).Infoln("Starting frontend server for client connections.")
131-
ps, err := server.GenProxyStrategiesFromStr(o.ProxyStrategies)
131+
ps, err := server.ParseProxyStrategies(o.ProxyStrategies)
132132
if err != nil {
133133
return err
134134
}

pkg/server/backend_manager.go

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,40 +36,66 @@ import (
3636
"sigs.k8s.io/apiserver-network-proxy/proto/header"
3737
)
3838

39-
type ProxyStrategy string
39+
type ProxyStrategy int
4040

4141
const (
4242
// With this strategy the Proxy Server will randomly pick a backend from
4343
// the current healthy backends to establish the tunnel over which to
4444
// forward requests.
45-
ProxyStrategyDefault ProxyStrategy = "default"
45+
ProxyStrategyDefault ProxyStrategy = iota + 1
4646
// With this strategy the Proxy Server will pick a backend that has the same
4747
// associated host as the request.Host to establish the tunnel.
48-
ProxyStrategyDestHost ProxyStrategy = "destHost"
49-
48+
ProxyStrategyDestHost
5049
// ProxyStrategyDefaultRoute will only forward traffic to agents that have explicity advertised
5150
// they serve the default route through an agent identifier. Typically used in combination with destHost
52-
ProxyStrategyDefaultRoute ProxyStrategy = "defaultRoute"
51+
ProxyStrategyDefaultRoute
5352
)
5453

54+
func (ps ProxyStrategy) String() string {
55+
switch ps {
56+
case ProxyStrategyDefault:
57+
return "default"
58+
case ProxyStrategyDestHost:
59+
return "destHost"
60+
case ProxyStrategyDefaultRoute:
61+
return "defaultRoute"
62+
}
63+
panic(fmt.Sprintf("unhandled ProxyStrategy: %d", ps))
64+
}
65+
66+
func ParseProxyStrategy(s string) (ProxyStrategy, error) {
67+
switch s {
68+
case ProxyStrategyDefault.String():
69+
return ProxyStrategyDefault, nil
70+
case ProxyStrategyDestHost.String():
71+
return ProxyStrategyDestHost, nil
72+
case ProxyStrategyDefaultRoute.String():
73+
return ProxyStrategyDefaultRoute, nil
74+
default:
75+
return 0, fmt.Errorf("unknown proxy strategy: %s", s)
76+
}
77+
}
78+
5579
// GenProxyStrategiesFromStr generates the list of proxy strategies from the
5680
// comma-seperated string, i.e., destHost.
57-
func GenProxyStrategiesFromStr(proxyStrategies string) ([]ProxyStrategy, error) {
58-
var ps []ProxyStrategy
81+
func ParseProxyStrategies(proxyStrategies string) ([]ProxyStrategy, error) {
82+
var result []ProxyStrategy
83+
5984
strs := strings.Split(proxyStrategies, ",")
6085
for _, s := range strs {
61-
switch s {
62-
case string(ProxyStrategyDestHost):
63-
ps = append(ps, ProxyStrategyDestHost)
64-
case string(ProxyStrategyDefault):
65-
ps = append(ps, ProxyStrategyDefault)
66-
case string(ProxyStrategyDefaultRoute):
67-
ps = append(ps, ProxyStrategyDefaultRoute)
68-
default:
69-
return nil, fmt.Errorf("Unknown proxy strategy %s", s)
86+
if len(s) == 0 {
87+
continue
7088
}
89+
ps, err := ParseProxyStrategy(s)
90+
if err != nil {
91+
return nil, err
92+
}
93+
result = append(result, ps)
94+
}
95+
if len(result) == 0 {
96+
return nil, fmt.Errorf("proxy strategies cannot be empty")
7197
}
72-
return ps, nil
98+
return result, nil
7399
}
74100

75101
// Backend abstracts a connected Konnectivity agent.

pkg/server/backend_manager_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package server
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"reflect"
2223
"testing"
2324

25+
"github.com/stretchr/testify/assert"
2426
"go.uber.org/mock/gomock"
2527
"google.golang.org/grpc/metadata"
2628

@@ -383,3 +385,123 @@ func TestDestHostBackendManager_WithDuplicateIdents(t *testing.T) {
383385
t.Errorf("expected %v, got %v", e, a)
384386
}
385387
}
388+
389+
func TestProxyStrategy(t *testing.T) {
390+
for desc, tc := range map[string]struct {
391+
input ProxyStrategy
392+
want string
393+
wantPanic string
394+
}{
395+
"default": {
396+
input: ProxyStrategyDefault,
397+
want: "default",
398+
},
399+
"destHost": {
400+
input: ProxyStrategyDestHost,
401+
want: "destHost",
402+
},
403+
"defaultRoute": {
404+
input: ProxyStrategyDefaultRoute,
405+
want: "defaultRoute",
406+
},
407+
"unrecognized": {
408+
input: ProxyStrategy(0),
409+
wantPanic: "unhandled ProxyStrategy: 0",
410+
},
411+
} {
412+
t.Run(desc, func(t *testing.T) {
413+
if tc.wantPanic != "" {
414+
assert.PanicsWithValue(t, tc.wantPanic, func() {
415+
_ = tc.input.String()
416+
})
417+
} else {
418+
got := tc.input.String()
419+
if got != tc.want {
420+
t.Errorf("ProxyStrategy.String(): got %v, want %v", got, tc.want)
421+
}
422+
}
423+
})
424+
}
425+
}
426+
427+
func TestParseProxyStrategy(t *testing.T) {
428+
for desc, tc := range map[string]struct {
429+
input string
430+
want ProxyStrategy
431+
wantErr error
432+
}{
433+
"empty": {
434+
input: "",
435+
wantErr: fmt.Errorf("unknown proxy strategy: "),
436+
},
437+
"unrecognized": {
438+
input: "unrecognized",
439+
wantErr: fmt.Errorf("unknown proxy strategy: unrecognized"),
440+
},
441+
"default": {
442+
input: "default",
443+
want: ProxyStrategyDefault,
444+
},
445+
"destHost": {
446+
input: "destHost",
447+
want: ProxyStrategyDestHost,
448+
},
449+
"defaultRoute": {
450+
input: "defaultRoute",
451+
want: ProxyStrategyDefaultRoute,
452+
},
453+
} {
454+
t.Run(desc, func(t *testing.T) {
455+
got, err := ParseProxyStrategy(tc.input)
456+
assert.Equal(t, tc.wantErr, err, "ParseProxyStrategy(%s): got error %q, want %v", tc.input, err, tc.wantErr)
457+
if got != tc.want {
458+
t.Errorf("ParseProxyStrategy(%s): got %v, want %v", tc.input, got, tc.want)
459+
}
460+
})
461+
}
462+
}
463+
464+
func TestParseProxyStrategies(t *testing.T) {
465+
for desc, tc := range map[string]struct {
466+
input string
467+
want []ProxyStrategy
468+
wantErr error
469+
}{
470+
"empty": {
471+
input: "",
472+
wantErr: fmt.Errorf("proxy strategies cannot be empty"),
473+
},
474+
"unrecognized": {
475+
input: "unrecognized",
476+
wantErr: fmt.Errorf("unknown proxy strategy: unrecognized"),
477+
},
478+
"default": {
479+
input: "default",
480+
want: []ProxyStrategy{ProxyStrategyDefault},
481+
},
482+
"destHost": {
483+
input: "destHost",
484+
want: []ProxyStrategy{ProxyStrategyDestHost},
485+
},
486+
"defaultRoute": {
487+
input: "defaultRoute",
488+
want: []ProxyStrategy{ProxyStrategyDefaultRoute},
489+
},
490+
"duplicate": {
491+
input: "destHost,defaultRoute,defaultRoute,default",
492+
want: []ProxyStrategy{ProxyStrategyDestHost, ProxyStrategyDefaultRoute, ProxyStrategyDefaultRoute, ProxyStrategyDefault},
493+
},
494+
"multiple": {
495+
input: "destHost,defaultRoute,default",
496+
want: []ProxyStrategy{ProxyStrategyDestHost, ProxyStrategyDefaultRoute, ProxyStrategyDefault},
497+
},
498+
} {
499+
t.Run(desc, func(t *testing.T) {
500+
got, err := ParseProxyStrategies(tc.input)
501+
assert.Equal(t, tc.wantErr, err, "ParseProxyStrategies(%s): got error %q, want %v", tc.input, err, tc.wantErr)
502+
if !reflect.DeepEqual(got, tc.want) {
503+
t.Errorf("ParseProxyStrategies(%s): got %v, want %v", tc.input, got, tc.want)
504+
}
505+
})
506+
}
507+
}

0 commit comments

Comments
 (0)