1/*
2 *
3 * Copyright 2017 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19package grpc
20
21import (
22	"encoding/json"
23	"fmt"
24	"math"
25	"reflect"
26	"testing"
27	"time"
28
29	"google.golang.org/grpc/balancer"
30	"google.golang.org/grpc/serviceconfig"
31)
32
33type parseTestCase struct {
34	scjs    string
35	wantSC  *ServiceConfig
36	wantErr bool
37}
38
39func runParseTests(t *testing.T, testCases []parseTestCase) {
40	t.Helper()
41	for _, c := range testCases {
42		scpr := parseServiceConfig(c.scjs)
43		var sc *ServiceConfig
44		sc, _ = scpr.Config.(*ServiceConfig)
45		if !c.wantErr {
46			c.wantSC.rawJSONString = c.scjs
47		}
48		if c.wantErr != (scpr.Err != nil) || !reflect.DeepEqual(sc, c.wantSC) {
49			t.Fatalf("parseServiceConfig(%s) = %+v, %v, want %+v, %v", c.scjs, sc, scpr.Err, c.wantSC, c.wantErr)
50		}
51	}
52}
53
54type pbbData struct {
55	serviceconfig.LoadBalancingConfig
56	Foo string
57	Bar int
58}
59
60type parseBalancerBuilder struct{}
61
62func (parseBalancerBuilder) Name() string {
63	return "pbb"
64}
65
66func (parseBalancerBuilder) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
67	d := pbbData{}
68	if err := json.Unmarshal(c, &d); err != nil {
69		return nil, err
70	}
71	return d, nil
72}
73
74func (parseBalancerBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
75	panic("unimplemented")
76}
77
78func init() {
79	balancer.Register(parseBalancerBuilder{})
80}
81
82func (s) TestParseLBConfig(t *testing.T) {
83	testcases := []parseTestCase{
84		{
85			`{
86    "loadBalancingConfig": [{"pbb": { "foo": "hi" } }]
87}`,
88			&ServiceConfig{
89				Methods:  make(map[string]MethodConfig),
90				lbConfig: &lbConfig{name: "pbb", cfg: pbbData{Foo: "hi"}},
91			},
92			false,
93		},
94	}
95	runParseTests(t, testcases)
96}
97
98func (s) TestParseNoLBConfigSupported(t *testing.T) {
99	// We have a loadBalancingConfig field but will not encounter a supported
100	// policy.  The config will be considered invalid in this case.
101	testcases := []parseTestCase{
102		{
103			scjs: `{
104    "loadBalancingConfig": [{"not_a_balancer1": {} }, {"not_a_balancer2": {}}]
105}`,
106			wantErr: true,
107		}, {
108			scjs:    `{"loadBalancingConfig": []}`,
109			wantErr: true,
110		},
111	}
112	runParseTests(t, testcases)
113}
114
115func (s) TestParseLoadBalancer(t *testing.T) {
116	testcases := []parseTestCase{
117		{
118			`{
119    "loadBalancingPolicy": "round_robin",
120    "methodConfig": [
121        {
122            "name": [
123                {
124                    "service": "foo",
125                    "method": "Bar"
126                }
127            ],
128            "waitForReady": true
129        }
130    ]
131}`,
132			&ServiceConfig{
133				LB: newString("round_robin"),
134				Methods: map[string]MethodConfig{
135					"/foo/Bar": {
136						WaitForReady: newBool(true),
137					},
138				},
139			},
140			false,
141		},
142		{
143			`{
144    "loadBalancingPolicy": 1,
145    "methodConfig": [
146        {
147            "name": [
148                {
149                    "service": "foo",
150                    "method": "Bar"
151                }
152            ],
153            "waitForReady": false
154        }
155    ]
156}`,
157			nil,
158			true,
159		},
160	}
161	runParseTests(t, testcases)
162}
163
164func (s) TestParseWaitForReady(t *testing.T) {
165	testcases := []parseTestCase{
166		{
167			`{
168    "methodConfig": [
169        {
170            "name": [
171                {
172                    "service": "foo",
173                    "method": "Bar"
174                }
175            ],
176            "waitForReady": true
177        }
178    ]
179}`,
180			&ServiceConfig{
181				Methods: map[string]MethodConfig{
182					"/foo/Bar": {
183						WaitForReady: newBool(true),
184					},
185				},
186			},
187			false,
188		},
189		{
190			`{
191    "methodConfig": [
192        {
193            "name": [
194                {
195                    "service": "foo",
196                    "method": "Bar"
197                }
198            ],
199            "waitForReady": false
200        }
201    ]
202}`,
203			&ServiceConfig{
204				Methods: map[string]MethodConfig{
205					"/foo/Bar": {
206						WaitForReady: newBool(false),
207					},
208				},
209			},
210			false,
211		},
212		{
213			`{
214    "methodConfig": [
215        {
216            "name": [
217                {
218                    "service": "foo",
219                    "method": "Bar"
220                }
221            ],
222            "waitForReady": fall
223        },
224        {
225            "name": [
226                {
227                    "service": "foo",
228                    "method": "Bar"
229                }
230            ],
231            "waitForReady": true
232        }
233    ]
234}`,
235			nil,
236			true,
237		},
238	}
239
240	runParseTests(t, testcases)
241}
242
243func (s) TestParseTimeOut(t *testing.T) {
244	testcases := []parseTestCase{
245		{
246			`{
247    "methodConfig": [
248        {
249            "name": [
250                {
251                    "service": "foo",
252                    "method": "Bar"
253                }
254            ],
255            "timeout": "1s"
256        }
257    ]
258}`,
259			&ServiceConfig{
260				Methods: map[string]MethodConfig{
261					"/foo/Bar": {
262						Timeout: newDuration(time.Second),
263					},
264				},
265			},
266			false,
267		},
268		{
269			`{
270    "methodConfig": [
271        {
272            "name": [
273                {
274                    "service": "foo",
275                    "method": "Bar"
276                }
277            ],
278            "timeout": "3c"
279        }
280    ]
281}`,
282			nil,
283			true,
284		},
285		{
286			`{
287    "methodConfig": [
288        {
289            "name": [
290                {
291                    "service": "foo",
292                    "method": "Bar"
293                }
294            ],
295            "timeout": "3c"
296        },
297        {
298            "name": [
299                {
300                    "service": "foo",
301                    "method": "Bar"
302                }
303            ],
304            "timeout": "1s"
305        }
306    ]
307}`,
308			nil,
309			true,
310		},
311	}
312
313	runParseTests(t, testcases)
314}
315
316func (s) TestParseMsgSize(t *testing.T) {
317	testcases := []parseTestCase{
318		{
319			`{
320    "methodConfig": [
321        {
322            "name": [
323                {
324                    "service": "foo",
325                    "method": "Bar"
326                }
327            ],
328            "maxRequestMessageBytes": 1024,
329            "maxResponseMessageBytes": 2048
330        }
331    ]
332}`,
333			&ServiceConfig{
334				Methods: map[string]MethodConfig{
335					"/foo/Bar": {
336						MaxReqSize:  newInt(1024),
337						MaxRespSize: newInt(2048),
338					},
339				},
340			},
341			false,
342		},
343		{
344			`{
345    "methodConfig": [
346        {
347            "name": [
348                {
349                    "service": "foo",
350                    "method": "Bar"
351                }
352            ],
353            "maxRequestMessageBytes": "1024",
354            "maxResponseMessageBytes": "2048"
355        },
356        {
357            "name": [
358                {
359                    "service": "foo",
360                    "method": "Bar"
361                }
362            ],
363            "maxRequestMessageBytes": 1024,
364            "maxResponseMessageBytes": 2048
365        }
366    ]
367}`,
368			nil,
369			true,
370		},
371	}
372
373	runParseTests(t, testcases)
374}
375func (s) TestParseDefaultMethodConfig(t *testing.T) {
376	dc := &ServiceConfig{
377		Methods: map[string]MethodConfig{
378			"": {WaitForReady: newBool(true)},
379		},
380	}
381
382	runParseTests(t, []parseTestCase{
383		{
384			`{
385  "methodConfig": [{
386    "name": [{}],
387    "waitForReady": true
388  }]
389}`,
390			dc,
391			false,
392		},
393		{
394			`{
395  "methodConfig": [{
396    "name": [{"service": null}],
397    "waitForReady": true
398  }]
399}`,
400			dc,
401			false,
402		},
403		{
404			`{
405  "methodConfig": [{
406    "name": [{"service": ""}],
407    "waitForReady": true
408  }]
409}`,
410			dc,
411			false,
412		},
413		{
414			`{
415  "methodConfig": [{
416    "name": [{"method": "Bar"}],
417    "waitForReady": true
418  }]
419}`,
420			nil,
421			true,
422		},
423		{
424			`{
425  "methodConfig": [{
426    "name": [{"service": "", "method": "Bar"}],
427    "waitForReady": true
428  }]
429}`,
430			nil,
431			true,
432		},
433	})
434}
435
436func (s) TestParseMethodConfigDuplicatedName(t *testing.T) {
437	runParseTests(t, []parseTestCase{
438		{
439			`{
440  "methodConfig": [{
441    "name": [
442      {"service": "foo"},
443      {"service": "foo"}
444    ],
445    "waitForReady": true
446  }]
447}`, nil, true,
448		},
449	})
450}
451
452func (s) TestParseDuration(t *testing.T) {
453	testCases := []struct {
454		s    *string
455		want *time.Duration
456		err  bool
457	}{
458		{s: nil, want: nil},
459		{s: newString("1s"), want: newDuration(time.Second)},
460		{s: newString("-1s"), want: newDuration(-time.Second)},
461		{s: newString("1.1s"), want: newDuration(1100 * time.Millisecond)},
462		{s: newString("1.s"), want: newDuration(time.Second)},
463		{s: newString("1.0s"), want: newDuration(time.Second)},
464		{s: newString(".002s"), want: newDuration(2 * time.Millisecond)},
465		{s: newString(".002000s"), want: newDuration(2 * time.Millisecond)},
466		{s: newString("0.003s"), want: newDuration(3 * time.Millisecond)},
467		{s: newString("0.000004s"), want: newDuration(4 * time.Microsecond)},
468		{s: newString("5000.000000009s"), want: newDuration(5000*time.Second + 9*time.Nanosecond)},
469		{s: newString("4999.999999999s"), want: newDuration(5000*time.Second - time.Nanosecond)},
470		{s: newString("1"), err: true},
471		{s: newString("s"), err: true},
472		{s: newString(".s"), err: true},
473		{s: newString("1 s"), err: true},
474		{s: newString(" 1s"), err: true},
475		{s: newString("1ms"), err: true},
476		{s: newString("1.1.1s"), err: true},
477		{s: newString("Xs"), err: true},
478		{s: newString("as"), err: true},
479		{s: newString(".0000000001s"), err: true},
480		{s: newString(fmt.Sprint(math.MaxInt32) + "s"), want: newDuration(math.MaxInt32 * time.Second)},
481		{s: newString(fmt.Sprint(int64(math.MaxInt32)+1) + "s"), err: true},
482	}
483	for _, tc := range testCases {
484		got, err := parseDuration(tc.s)
485		if tc.err != (err != nil) ||
486			(got == nil) != (tc.want == nil) ||
487			(got != nil && *got != *tc.want) {
488			wantErr := "<nil>"
489			if tc.err {
490				wantErr = "<non-nil error>"
491			}
492			s := "<nil>"
493			if tc.s != nil {
494				s = `&"` + *tc.s + `"`
495			}
496			t.Errorf("parseDuration(%v) = %v, %v; want %v, %v", s, got, err, tc.want, wantErr)
497		}
498	}
499}
500
501func newBool(b bool) *bool {
502	return &b
503}
504
505func newDuration(b time.Duration) *time.Duration {
506	return &b
507}
508
509func newString(b string) *string {
510	return &b
511}
512