google.golang.org/grpc@v1.72.2/service_config_test.go (about)

     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  
    19  package grpc
    20  
    21  import (
    22  	"encoding/json"
    23  	"fmt"
    24  	"reflect"
    25  	"testing"
    26  	"time"
    27  
    28  	"google.golang.org/grpc/balancer"
    29  	"google.golang.org/grpc/codes"
    30  	"google.golang.org/grpc/internal/balancer/gracefulswitch"
    31  	"google.golang.org/grpc/serviceconfig"
    32  
    33  	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
    34  )
    35  
    36  type parseTestCase struct {
    37  	name    string
    38  	scjs    string
    39  	wantSC  *ServiceConfig
    40  	wantErr bool
    41  }
    42  
    43  func lbConfigFor(t *testing.T, name string, cfg serviceconfig.LoadBalancingConfig) serviceconfig.LoadBalancingConfig {
    44  	if name == "" {
    45  		name = "pick_first"
    46  		cfg = struct {
    47  			serviceconfig.LoadBalancingConfig
    48  		}{}
    49  	}
    50  	d := []map[string]any{{name: cfg}}
    51  	strCfg, err := json.Marshal(d)
    52  	t.Logf("strCfg = %v", string(strCfg))
    53  	if err != nil {
    54  		t.Fatalf("Error parsing config: %v", err)
    55  	}
    56  	parsedCfg, err := gracefulswitch.ParseConfig(strCfg)
    57  	if err != nil {
    58  		t.Fatalf("Error parsing config: %v", err)
    59  	}
    60  	return parsedCfg
    61  }
    62  
    63  func runParseTests(t *testing.T, testCases []parseTestCase) {
    64  	t.Helper()
    65  	for i, c := range testCases {
    66  		name := c.name
    67  		if name == "" {
    68  			name = fmt.Sprint(i)
    69  		}
    70  		t.Run(name, func(t *testing.T) {
    71  			scpr := parseServiceConfig(c.scjs, defaultMaxCallAttempts)
    72  			var sc *ServiceConfig
    73  			sc, _ = scpr.Config.(*ServiceConfig)
    74  			if !c.wantErr {
    75  				c.wantSC.rawJSONString = c.scjs
    76  			}
    77  			if c.wantErr != (scpr.Err != nil) || !reflect.DeepEqual(sc, c.wantSC) {
    78  				t.Fatalf("parseServiceConfig(%s) = %+v, %v, want %+v, %v", c.scjs, sc, scpr.Err, c.wantSC, c.wantErr)
    79  			}
    80  		})
    81  	}
    82  }
    83  
    84  type pbbData struct {
    85  	serviceconfig.LoadBalancingConfig
    86  	Foo string
    87  	Bar int
    88  }
    89  
    90  type parseBalancerBuilder struct{}
    91  
    92  func (parseBalancerBuilder) Name() string {
    93  	return "pbb"
    94  }
    95  
    96  func (parseBalancerBuilder) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
    97  	d := pbbData{}
    98  	if err := json.Unmarshal(c, &d); err != nil {
    99  		return nil, err
   100  	}
   101  	return d, nil
   102  }
   103  
   104  func (parseBalancerBuilder) Build(balancer.ClientConn, balancer.BuildOptions) balancer.Balancer {
   105  	panic("unimplemented")
   106  }
   107  
   108  func init() {
   109  	balancer.Register(parseBalancerBuilder{})
   110  }
   111  
   112  func (s) TestParseLBConfig(t *testing.T) {
   113  	testcases := []parseTestCase{
   114  		{
   115  			scjs: `{
   116      "loadBalancingConfig": [{"pbb": { "foo": "hi" } }]
   117  }`,
   118  			wantSC: &ServiceConfig{
   119  				Methods:  make(map[string]MethodConfig),
   120  				lbConfig: lbConfigFor(t, "pbb", pbbData{Foo: "hi"}),
   121  			},
   122  			wantErr: false,
   123  		},
   124  	}
   125  	runParseTests(t, testcases)
   126  }
   127  
   128  func (s) TestParseNoLBConfigSupported(t *testing.T) {
   129  	// We have a loadBalancingConfig field but will not encounter a supported
   130  	// policy.  The config will be considered invalid in this case.
   131  	testcases := []parseTestCase{
   132  		{
   133  			scjs: `{
   134      "loadBalancingConfig": [{"not_a_balancer1": {} }, {"not_a_balancer2": {}}]
   135  }`,
   136  			wantErr: true,
   137  		}, {
   138  			scjs:    `{"loadBalancingConfig": []}`,
   139  			wantErr: true,
   140  		},
   141  	}
   142  	runParseTests(t, testcases)
   143  }
   144  
   145  func (s) TestParseLoadBalancer(t *testing.T) {
   146  	testcases := []parseTestCase{
   147  		{
   148  			scjs: `{
   149      "loadBalancingPolicy": "round_robin",
   150      "methodConfig": [
   151          {
   152              "name": [
   153                  {
   154                      "service": "foo",
   155                      "method": "Bar"
   156                  }
   157              ],
   158              "waitForReady": true
   159          }
   160      ]
   161  }`,
   162  			wantSC: &ServiceConfig{
   163  				Methods: map[string]MethodConfig{
   164  					"/foo/Bar": {
   165  						WaitForReady: newBool(true),
   166  					},
   167  				},
   168  				lbConfig: lbConfigFor(t, "round_robin", nil),
   169  			},
   170  			wantErr: false,
   171  		},
   172  		{
   173  			scjs: `{
   174      "loadBalancingPolicy": 1,
   175      "methodConfig": [
   176          {
   177              "name": [
   178                  {
   179                      "service": "foo",
   180                      "method": "Bar"
   181                  }
   182              ],
   183              "waitForReady": false
   184          }
   185      ]
   186  }`,
   187  			wantErr: true,
   188  		},
   189  	}
   190  	runParseTests(t, testcases)
   191  }
   192  
   193  func (s) TestParseWaitForReady(t *testing.T) {
   194  	testcases := []parseTestCase{
   195  		{
   196  			scjs: `{
   197      "methodConfig": [
   198          {
   199              "name": [
   200                  {
   201                      "service": "foo",
   202                      "method": "Bar"
   203                  }
   204              ],
   205              "waitForReady": true
   206          }
   207      ]
   208  }`,
   209  			wantSC: &ServiceConfig{
   210  				Methods: map[string]MethodConfig{
   211  					"/foo/Bar": {
   212  						WaitForReady: newBool(true),
   213  					},
   214  				},
   215  				lbConfig: lbConfigFor(t, "", nil),
   216  			},
   217  		},
   218  		{
   219  			scjs: `{
   220      "methodConfig": [
   221          {
   222              "name": [
   223                  {
   224                      "service": "foo",
   225                      "method": "Bar"
   226                  }
   227              ],
   228              "waitForReady": false
   229          }
   230      ]
   231  }`,
   232  			wantSC: &ServiceConfig{
   233  				Methods: map[string]MethodConfig{
   234  					"/foo/Bar": {
   235  						WaitForReady: newBool(false),
   236  					},
   237  				},
   238  				lbConfig: lbConfigFor(t, "", nil),
   239  			},
   240  		},
   241  		{
   242  			scjs: `{
   243      "methodConfig": [
   244          {
   245              "name": [
   246                  {
   247                      "service": "foo",
   248                      "method": "Bar"
   249                  }
   250              ],
   251              "waitForReady": fall
   252          },
   253          {
   254              "name": [
   255                  {
   256                      "service": "foo",
   257                      "method": "Bar"
   258                  }
   259              ],
   260              "waitForReady": true
   261          }
   262      ]
   263  }`,
   264  			wantErr: true,
   265  		},
   266  	}
   267  
   268  	runParseTests(t, testcases)
   269  }
   270  
   271  func (s) TestParseTimeOut(t *testing.T) {
   272  	testcases := []parseTestCase{
   273  		{
   274  			scjs: `{
   275      "methodConfig": [
   276          {
   277              "name": [
   278                  {
   279                      "service": "foo",
   280                      "method": "Bar"
   281                  }
   282              ],
   283              "timeout": "1s"
   284          }
   285      ]
   286  }`,
   287  			wantSC: &ServiceConfig{
   288  				Methods: map[string]MethodConfig{
   289  					"/foo/Bar": {
   290  						Timeout: newDuration(time.Second),
   291  					},
   292  				},
   293  				lbConfig: lbConfigFor(t, "", nil),
   294  			},
   295  		},
   296  		{
   297  			scjs: `{
   298      "methodConfig": [
   299          {
   300              "name": [
   301                  {
   302                      "service": "foo",
   303                      "method": "Bar"
   304                  }
   305              ],
   306              "timeout": "3c"
   307          }
   308      ]
   309  }`,
   310  			wantErr: true,
   311  		},
   312  		{
   313  			scjs: `{
   314      "methodConfig": [
   315          {
   316              "name": [
   317                  {
   318                      "service": "foo",
   319                      "method": "Bar"
   320                  }
   321              ],
   322              "timeout": "3c"
   323          },
   324          {
   325              "name": [
   326                  {
   327                      "service": "foo",
   328                      "method": "Bar"
   329                  }
   330              ],
   331              "timeout": "1s"
   332          }
   333      ]
   334  }`,
   335  			wantErr: true,
   336  		},
   337  	}
   338  
   339  	runParseTests(t, testcases)
   340  }
   341  
   342  func (s) TestParseMsgSize(t *testing.T) {
   343  	testcases := []parseTestCase{
   344  		{
   345  			scjs: `{
   346      "methodConfig": [
   347          {
   348              "name": [
   349                  {
   350                      "service": "foo",
   351                      "method": "Bar"
   352                  }
   353              ],
   354              "maxRequestMessageBytes": 1024,
   355              "maxResponseMessageBytes": 2048
   356          }
   357      ]
   358  }`,
   359  			wantSC: &ServiceConfig{
   360  				Methods: map[string]MethodConfig{
   361  					"/foo/Bar": {
   362  						MaxReqSize:  newInt(1024),
   363  						MaxRespSize: newInt(2048),
   364  					},
   365  				},
   366  				lbConfig: lbConfigFor(t, "", nil),
   367  			},
   368  		},
   369  		{
   370  			scjs: `{
   371      "methodConfig": [
   372          {
   373              "name": [
   374                  {
   375                      "service": "foo",
   376                      "method": "Bar"
   377                  }
   378              ],
   379              "maxRequestMessageBytes": "1024",
   380              "maxResponseMessageBytes": "2048"
   381          },
   382          {
   383              "name": [
   384                  {
   385                      "service": "foo",
   386                      "method": "Bar"
   387                  }
   388              ],
   389              "maxRequestMessageBytes": 1024,
   390              "maxResponseMessageBytes": 2048
   391          }
   392      ]
   393  }`,
   394  			wantErr: true,
   395  		},
   396  	}
   397  
   398  	runParseTests(t, testcases)
   399  }
   400  func (s) TestParseDefaultMethodConfig(t *testing.T) {
   401  	dc := &ServiceConfig{
   402  		Methods: map[string]MethodConfig{
   403  			"": {WaitForReady: newBool(true)},
   404  		},
   405  		lbConfig: lbConfigFor(t, "", nil),
   406  	}
   407  
   408  	runParseTests(t, []parseTestCase{
   409  		{
   410  			scjs: `{
   411    "methodConfig": [{
   412      "name": [{}],
   413      "waitForReady": true
   414    }]
   415  }`,
   416  			wantSC: dc,
   417  		},
   418  		{
   419  			scjs: `{
   420    "methodConfig": [{
   421      "name": [{"service": null}],
   422      "waitForReady": true
   423    }]
   424  }`,
   425  			wantSC: dc,
   426  		},
   427  		{
   428  			scjs: `{
   429    "methodConfig": [{
   430      "name": [{"service": ""}],
   431      "waitForReady": true
   432    }]
   433  }`,
   434  			wantSC: dc,
   435  		},
   436  		{
   437  			scjs: `{
   438    "methodConfig": [{
   439      "name": [{"method": "Bar"}],
   440      "waitForReady": true
   441    }]
   442  }`,
   443  			wantErr: true,
   444  		},
   445  		{
   446  			scjs: `{
   447    "methodConfig": [{
   448      "name": [{"service": "", "method": "Bar"}],
   449      "waitForReady": true
   450    }]
   451  }`,
   452  			wantErr: true,
   453  		},
   454  	})
   455  }
   456  
   457  func (s) TestParseMethodConfigDuplicatedName(t *testing.T) {
   458  	runParseTests(t, []parseTestCase{
   459  		{
   460  			scjs: `{
   461    "methodConfig": [{
   462      "name": [
   463        {"service": "foo"},
   464        {"service": "foo"}
   465      ],
   466      "waitForReady": true
   467    }]
   468  }`,
   469  			wantErr: true,
   470  		},
   471  	})
   472  }
   473  
   474  func (s) TestParseRetryPolicy(t *testing.T) {
   475  	runParseTests(t, []parseTestCase{
   476  		{
   477  			name: "valid",
   478  			scjs: `{
   479  				"methodConfig": [{
   480  				  "name": [{"service": "foo"}],
   481  				  "retryPolicy": {
   482  					"maxAttempts": 2,
   483  					"initialBackoff": "2s",
   484  					"maxBackoff": "10s",
   485  					"backoffMultiplier": 2,
   486  					"retryableStatusCodes": ["UNAVAILABLE"]
   487  				  }
   488  				}]
   489  			  }`,
   490  			wantSC: &ServiceConfig{
   491  				Methods: map[string]MethodConfig{
   492  					"/foo/": {
   493  						RetryPolicy: &internalserviceconfig.RetryPolicy{
   494  							MaxAttempts:          2,
   495  							InitialBackoff:       2 * time.Second,
   496  							MaxBackoff:           10 * time.Second,
   497  							BackoffMultiplier:    2,
   498  							RetryableStatusCodes: map[codes.Code]bool{codes.Unavailable: true},
   499  						},
   500  					},
   501  				},
   502  				lbConfig: lbConfigFor(t, "", nil),
   503  			},
   504  		},
   505  		{
   506  			name: "negative maxAttempts",
   507  			scjs: `{
   508  				"methodConfig": [{
   509  				  "name": [{"service": "foo"}],
   510  				  "retryPolicy": {
   511  					  "maxAttempts": -1,
   512  					  "initialBackoff": "2s",
   513  					  "maxBackoff": "10s",
   514  					  "backoffMultiplier": 2,
   515  					  "retryableStatusCodes": ["UNAVAILABLE"]
   516  				  }
   517  				}]
   518  			  }`,
   519  			wantErr: true,
   520  		},
   521  		{
   522  			name: "missing maxAttempts",
   523  			scjs: `{
   524  				"methodConfig": [{
   525  				  "name": [{"service": "foo"}],
   526  				  "retryPolicy": {
   527  					  "initialBackoff": "2s",
   528  					  "maxBackoff": "10s",
   529  					  "backoffMultiplier": 2,
   530  					  "retryableStatusCodes": ["UNAVAILABLE"]
   531  				  }
   532  				}]
   533  			  }`,
   534  			wantErr: true,
   535  		},
   536  		{
   537  			name: "zero initialBackoff",
   538  			scjs: `{
   539  				"methodConfig": [{
   540  				  "name": [{"service": "foo"}],
   541  				  "retryPolicy": {
   542  					  "maxAttempts": 2,
   543  					  "initialBackoff": "0s",
   544  					  "maxBackoff": "10s",
   545  					  "backoffMultiplier": 2,
   546  					  "retryableStatusCodes": ["UNAVAILABLE"]
   547  				  }
   548  				}]
   549  			  }`,
   550  			wantErr: true,
   551  		},
   552  		{
   553  			name: "zero maxBackoff",
   554  			scjs: `{
   555  				"methodConfig": [{
   556  				  "name": [{"service": "foo"}],
   557  				  "retryPolicy": {
   558  					  "maxAttempts": 2,
   559  					  "initialBackoff": "2s",
   560  					  "maxBackoff": "0s",
   561  					  "backoffMultiplier": 2,
   562  					  "retryableStatusCodes": ["UNAVAILABLE"]
   563  				  }
   564  				}]
   565  			  }`,
   566  			wantErr: true,
   567  		},
   568  		{
   569  			name: "zero backoffMultiplier",
   570  			scjs: `{
   571  				"methodConfig": [{
   572  				  "name": [{"service": "foo"}],
   573  				  "retryPolicy": {
   574  					  "maxAttempts": 2,
   575  					  "initialBackoff": "2s",
   576  					  "maxBackoff": "10s",
   577  					  "backoffMultiplier": 0,
   578  					  "retryableStatusCodes": ["UNAVAILABLE"]
   579  				  }
   580  				}]
   581  			  }`,
   582  			wantErr: true,
   583  		},
   584  		{
   585  			name: "no retryable codes",
   586  			scjs: `{
   587  				"methodConfig": [{
   588  				  "name": [{"service": "foo"}],
   589  				  "retryPolicy": {
   590  					  "maxAttempts": 2,
   591  					  "initialBackoff": "2s",
   592  					  "maxBackoff": "10s",
   593  					  "backoffMultiplier": 2,
   594  					  "retryableStatusCodes": []
   595  				  }
   596  				}]
   597  			  }`,
   598  			wantErr: true,
   599  		},
   600  	})
   601  }
   602  
   603  func newBool(b bool) *bool {
   604  	return &b
   605  }
   606  
   607  func newDuration(b time.Duration) *time.Duration {
   608  	return &b
   609  }