google.golang.org/grpc@v1.72.2/balancer/rls/config_test.go (about) 1 /* 2 * 3 * Copyright 2020 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 rls 20 21 import ( 22 "encoding/json" 23 "fmt" 24 "strings" 25 "testing" 26 "time" 27 28 _ "google.golang.org/grpc/balancer/grpclb" // grpclb for config parsing. 29 _ "google.golang.org/grpc/internal/resolver/passthrough" // passthrough resolver. 30 ) 31 32 // testEqual reports whether the lbCfgs a and b are equal. This is to be used 33 // only from tests. This ignores the keyBuilderMap field because its internals 34 // are not exported, and hence not possible to specify in the want section of 35 // the test. This is fine because we already have tests to make sure that the 36 // keyBuilder is parsed properly from the service config. 37 func testEqual(a, b *lbConfig) bool { 38 return a.lookupService == b.lookupService && 39 a.lookupServiceTimeout == b.lookupServiceTimeout && 40 a.maxAge == b.maxAge && 41 a.staleAge == b.staleAge && 42 a.cacheSizeBytes == b.cacheSizeBytes && 43 a.defaultTarget == b.defaultTarget && 44 a.controlChannelServiceConfig == b.controlChannelServiceConfig && 45 a.childPolicyName == b.childPolicyName && 46 a.childPolicyTargetField == b.childPolicyTargetField && 47 childPolicyConfigEqual(a.childPolicyConfig, b.childPolicyConfig) 48 } 49 50 // TestParseConfig verifies successful config parsing scenarios. 51 func (s) TestParseConfig(t *testing.T) { 52 childPolicyTargetFieldVal, _ := json.Marshal(dummyChildPolicyTarget) 53 tests := []struct { 54 desc string 55 input []byte 56 wantCfg *lbConfig 57 }{ 58 { 59 // This input validates a few cases: 60 // - A top-level unknown field should not fail. 61 // - An unknown field in routeLookupConfig proto should not fail. 62 // - lookupServiceTimeout is set to its default value, since it is not specified in the input. 63 // - maxAge is clamped to maxMaxAge if staleAge is not set. 64 // - staleAge is ignored because it is higher than maxAge in the input. 65 // - cacheSizeBytes is greater than the hard upper limit of 5MB 66 desc: "with transformations 1", 67 input: []byte(`{ 68 "top-level-unknown-field": "unknown-value", 69 "routeLookupConfig": { 70 "unknown-field": "unknown-value", 71 "grpcKeybuilders": [{ 72 "names": [{"service": "service", "method": "method"}], 73 "headers": [{"key": "k1", "names": ["v1"]}] 74 }], 75 "lookupService": ":///target", 76 "maxAge" : "500s", 77 "staleAge": "600s", 78 "cacheSizeBytes": 100000000, 79 "defaultTarget": "passthrough:///default" 80 }, 81 "childPolicy": [ 82 {"cds_experimental": {"Cluster": "my-fav-cluster"}}, 83 {"unknown-policy": {"unknown-field": "unknown-value"}}, 84 {"grpclb": {"childPolicy": [{"pickfirst": {}}]}} 85 ], 86 "childPolicyConfigTargetFieldName": "serviceName" 87 }`), 88 wantCfg: &lbConfig{ 89 lookupService: ":///target", 90 lookupServiceTimeout: 10 * time.Second, // This is the default value. 91 maxAge: 500 * time.Second, // Max age is not clamped when stale age is set. 92 staleAge: 300 * time.Second, // StaleAge is clamped because it was higher than maxMaxAge. 93 cacheSizeBytes: maxCacheSize, 94 defaultTarget: "passthrough:///default", 95 childPolicyName: "grpclb", 96 childPolicyTargetField: "serviceName", 97 childPolicyConfig: map[string]json.RawMessage{ 98 "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`), 99 "serviceName": json.RawMessage(childPolicyTargetFieldVal), 100 }, 101 }, 102 }, 103 { 104 desc: "maxAge not clamped when staleAge is set", 105 input: []byte(`{ 106 "routeLookupConfig": { 107 "grpcKeybuilders": [{ 108 "names": [{"service": "service", "method": "method"}], 109 "headers": [{"key": "k1", "names": ["v1"]}] 110 }], 111 "lookupService": ":///target", 112 "maxAge" : "500s", 113 "staleAge": "200s", 114 "cacheSizeBytes": 100000000 115 }, 116 "childPolicy": [ 117 {"grpclb": {"childPolicy": [{"pickfirst": {}}]}} 118 ], 119 "childPolicyConfigTargetFieldName": "serviceName" 120 }`), 121 wantCfg: &lbConfig{ 122 lookupService: ":///target", 123 lookupServiceTimeout: 10 * time.Second, // This is the default value. 124 maxAge: 500 * time.Second, // Max age is not clamped when stale age is set. 125 staleAge: 200 * time.Second, // This is stale age within maxMaxAge. 126 cacheSizeBytes: maxCacheSize, 127 childPolicyName: "grpclb", 128 childPolicyTargetField: "serviceName", 129 childPolicyConfig: map[string]json.RawMessage{ 130 "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`), 131 "serviceName": json.RawMessage(childPolicyTargetFieldVal), 132 }, 133 }, 134 }, 135 { 136 desc: "maxAge clamped when staleAge is not set", 137 input: []byte(`{ 138 "routeLookupConfig": { 139 "grpcKeybuilders": [{ 140 "names": [{"service": "service", "method": "method"}], 141 "headers": [{"key": "k1", "names": ["v1"]}] 142 }], 143 "lookupService": ":///target", 144 "maxAge" : "500s", 145 "cacheSizeBytes": 100000000 146 }, 147 "childPolicy": [ 148 {"grpclb": {"childPolicy": [{"pickfirst": {}}]}} 149 ], 150 "childPolicyConfigTargetFieldName": "serviceName" 151 }`), 152 wantCfg: &lbConfig{ 153 lookupService: ":///target", 154 lookupServiceTimeout: 10 * time.Second, // This is the default value. 155 maxAge: 300 * time.Second, // Max age is clamped when stale age is not set. 156 staleAge: 300 * time.Second, 157 cacheSizeBytes: maxCacheSize, 158 childPolicyName: "grpclb", 159 childPolicyTargetField: "serviceName", 160 childPolicyConfig: map[string]json.RawMessage{ 161 "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`), 162 "serviceName": json.RawMessage(childPolicyTargetFieldVal), 163 }, 164 }, 165 }, 166 { 167 desc: "without transformations", 168 input: []byte(`{ 169 "routeLookupConfig": { 170 "grpcKeybuilders": [{ 171 "names": [{"service": "service", "method": "method"}], 172 "headers": [{"key": "k1", "names": ["v1"]}] 173 }], 174 "lookupService": "target", 175 "lookupServiceTimeout" : "100s", 176 "maxAge": "60s", 177 "staleAge" : "50s", 178 "cacheSizeBytes": 1000, 179 "defaultTarget": "passthrough:///default" 180 }, 181 "routeLookupChannelServiceConfig": {"loadBalancingConfig": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}]}, 182 "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}], 183 "childPolicyConfigTargetFieldName": "serviceName" 184 }`), 185 wantCfg: &lbConfig{ 186 lookupService: "target", 187 lookupServiceTimeout: 100 * time.Second, 188 maxAge: 60 * time.Second, 189 staleAge: 50 * time.Second, 190 cacheSizeBytes: 1000, 191 defaultTarget: "passthrough:///default", 192 controlChannelServiceConfig: `{"loadBalancingConfig": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}]}`, 193 childPolicyName: "grpclb", 194 childPolicyTargetField: "serviceName", 195 childPolicyConfig: map[string]json.RawMessage{ 196 "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`), 197 "serviceName": json.RawMessage(childPolicyTargetFieldVal), 198 }, 199 }, 200 }, 201 } 202 203 builder := rlsBB{} 204 for _, test := range tests { 205 t.Run(test.desc, func(t *testing.T) { 206 lbCfg, err := builder.ParseConfig(test.input) 207 if err != nil || !testEqual(lbCfg.(*lbConfig), test.wantCfg) { 208 t.Errorf("ParseConfig(%s) = {%+v, %v}, want {%+v, nil}", string(test.input), lbCfg, err, test.wantCfg) 209 } 210 }) 211 } 212 } 213 214 // TestParseConfigErrors verifies config parsing failure scenarios. 215 func (s) TestParseConfigErrors(t *testing.T) { 216 tests := []struct { 217 desc string 218 input []byte 219 wantErr string 220 }{ 221 { 222 desc: "empty input", 223 input: nil, 224 wantErr: "rls: json unmarshal failed for service config", 225 }, 226 { 227 desc: "bad json", 228 input: []byte(`bad bad json`), 229 wantErr: "rls: json unmarshal failed for service config", 230 }, 231 { 232 desc: "bad grpcKeyBuilder", 233 input: []byte(`{ 234 "routeLookupConfig": { 235 "grpcKeybuilders": [{ 236 "names": [{"service": "service", "method": "method"}], 237 "headers": [{"key": "k1", "requiredMatch": true, "names": ["v1"]}] 238 }] 239 } 240 }`), 241 wantErr: "rls: GrpcKeyBuilder in RouteLookupConfig has required_match field set", 242 }, 243 { 244 desc: "empty lookup service", 245 input: []byte(`{ 246 "routeLookupConfig": { 247 "grpcKeybuilders": [{ 248 "names": [{"service": "service", "method": "method"}], 249 "headers": [{"key": "k1", "names": ["v1"]}] 250 }] 251 } 252 }`), 253 wantErr: "rls: empty lookup_service in route lookup config", 254 }, 255 { 256 desc: "unregistered scheme in lookup service URI", 257 input: []byte(`{ 258 "routeLookupConfig": { 259 "grpcKeybuilders": [{ 260 "names": [{"service": "service", "method": "method"}], 261 "headers": [{"key": "k1", "names": ["v1"]}] 262 }], 263 "lookupService": "badScheme:///target" 264 } 265 }`), 266 wantErr: "rls: unregistered scheme in lookup_service", 267 }, 268 { 269 desc: "invalid lookup service timeout", 270 input: []byte(`{ 271 "routeLookupConfig": { 272 "grpcKeybuilders": [{ 273 "names": [{"service": "service", "method": "method"}], 274 "headers": [{"key": "k1", "names": ["v1"]}] 275 }], 276 "lookupService": "passthrough:///target", 277 "lookupServiceTimeout" : "315576000001s" 278 } 279 }`), 280 wantErr: "google.protobuf.Duration value out of range", 281 }, 282 { 283 desc: "invalid max age", 284 input: []byte(`{ 285 "routeLookupConfig": { 286 "grpcKeybuilders": [{ 287 "names": [{"service": "service", "method": "method"}], 288 "headers": [{"key": "k1", "names": ["v1"]}] 289 }], 290 "lookupService": "passthrough:///target", 291 "lookupServiceTimeout" : "10s", 292 "maxAge" : "315576000001s" 293 } 294 }`), 295 wantErr: "google.protobuf.Duration value out of range", 296 }, 297 { 298 desc: "invalid stale age", 299 input: []byte(`{ 300 "routeLookupConfig": { 301 "grpcKeybuilders": [{ 302 "names": [{"service": "service", "method": "method"}], 303 "headers": [{"key": "k1", "names": ["v1"]}] 304 }], 305 "lookupService": "passthrough:///target", 306 "lookupServiceTimeout" : "10s", 307 "maxAge" : "10s", 308 "staleAge" : "315576000001s" 309 } 310 }`), 311 wantErr: "google.protobuf.Duration value out of range", 312 }, 313 { 314 desc: "invalid max age stale age combo", 315 input: []byte(`{ 316 "routeLookupConfig": { 317 "grpcKeybuilders": [{ 318 "names": [{"service": "service", "method": "method"}], 319 "headers": [{"key": "k1", "names": ["v1"]}] 320 }], 321 "lookupService": "passthrough:///target", 322 "lookupServiceTimeout" : "10s", 323 "staleAge" : "10s" 324 } 325 }`), 326 wantErr: "rls: stale_age is set, but max_age is not in route lookup config", 327 }, 328 { 329 desc: "cache_size_bytes field is not set", 330 input: []byte(`{ 331 "routeLookupConfig": { 332 "grpcKeybuilders": [{ 333 "names": [{"service": "service", "method": "method"}], 334 "headers": [{"key": "k1", "names": ["v1"]}] 335 }], 336 "lookupService": "passthrough:///target", 337 "lookupServiceTimeout" : "10s", 338 "maxAge": "30s", 339 "staleAge" : "25s", 340 "defaultTarget": "passthrough:///default" 341 }, 342 "childPolicyConfigTargetFieldName": "serviceName" 343 }`), 344 wantErr: "rls: cache_size_bytes must be set to a non-zero value", 345 }, 346 { 347 desc: "routeLookupChannelServiceConfig is not in service config format", 348 input: []byte(`{ 349 "routeLookupConfig": { 350 "grpcKeybuilders": [{ 351 "names": [{"service": "service", "method": "method"}], 352 "headers": [{"key": "k1", "names": ["v1"]}] 353 }], 354 "lookupService": "target", 355 "lookupServiceTimeout" : "100s", 356 "maxAge": "60s", 357 "staleAge" : "50s", 358 "cacheSizeBytes": 1000, 359 "defaultTarget": "passthrough:///default" 360 }, 361 "routeLookupChannelServiceConfig": "unknown", 362 "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}], 363 "childPolicyConfigTargetFieldName": "serviceName" 364 }`), 365 wantErr: "cannot unmarshal string into Go value of type grpc.jsonSC", 366 }, 367 { 368 desc: "routeLookupChannelServiceConfig contains unknown LB policy", 369 input: []byte(`{ 370 "routeLookupConfig": { 371 "grpcKeybuilders": [{ 372 "names": [{"service": "service", "method": "method"}], 373 "headers": [{"key": "k1", "names": ["v1"]}] 374 }], 375 "lookupService": "target", 376 "lookupServiceTimeout" : "100s", 377 "maxAge": "60s", 378 "staleAge" : "50s", 379 "cacheSizeBytes": 1000, 380 "defaultTarget": "passthrough:///default" 381 }, 382 "routeLookupChannelServiceConfig": { 383 "loadBalancingConfig": [{"not_a_balancer1": {} }, {"not_a_balancer2": {}}] 384 }, 385 "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}], 386 "childPolicyConfigTargetFieldName": "serviceName" 387 }`), 388 wantErr: "no supported policies found in config", 389 }, 390 { 391 desc: "no child policy", 392 input: []byte(`{ 393 "routeLookupConfig": { 394 "grpcKeybuilders": [{ 395 "names": [{"service": "service", "method": "method"}], 396 "headers": [{"key": "k1", "names": ["v1"]}] 397 }], 398 "lookupService": "passthrough:///target", 399 "lookupServiceTimeout" : "10s", 400 "maxAge": "30s", 401 "staleAge" : "25s", 402 "cacheSizeBytes": 1000, 403 "defaultTarget": "passthrough:///default" 404 }, 405 "childPolicyConfigTargetFieldName": "serviceName" 406 }`), 407 wantErr: "rls: invalid childPolicy config: no supported policies found", 408 }, 409 { 410 desc: "no known child policy", 411 input: []byte(`{ 412 "routeLookupConfig": { 413 "grpcKeybuilders": [{ 414 "names": [{"service": "service", "method": "method"}], 415 "headers": [{"key": "k1", "names": ["v1"]}] 416 }], 417 "lookupService": "passthrough:///target", 418 "lookupServiceTimeout" : "10s", 419 "maxAge": "30s", 420 "staleAge" : "25s", 421 "cacheSizeBytes": 1000, 422 "defaultTarget": "passthrough:///default" 423 }, 424 "childPolicy": [ 425 {"cds_experimental": {"Cluster": "my-fav-cluster"}}, 426 {"unknown-policy": {"unknown-field": "unknown-value"}} 427 ], 428 "childPolicyConfigTargetFieldName": "serviceName" 429 }`), 430 wantErr: "rls: invalid childPolicy config: no supported policies found", 431 }, 432 { 433 desc: "invalid child policy config - more than one entry in map", 434 input: []byte(`{ 435 "routeLookupConfig": { 436 "grpcKeybuilders": [{ 437 "names": [{"service": "service", "method": "method"}], 438 "headers": [{"key": "k1", "names": ["v1"]}] 439 }], 440 "lookupService": "passthrough:///target", 441 "lookupServiceTimeout" : "10s", 442 "maxAge": "30s", 443 "staleAge" : "25s", 444 "cacheSizeBytes": 1000, 445 "defaultTarget": "passthrough:///default" 446 }, 447 "childPolicy": [ 448 { 449 "cds_experimental": {"Cluster": "my-fav-cluster"}, 450 "unknown-policy": {"unknown-field": "unknown-value"} 451 } 452 ], 453 "childPolicyConfigTargetFieldName": "serviceName" 454 }`), 455 wantErr: "does not contain exactly 1 policy/config pair", 456 }, 457 { 458 desc: "no childPolicyConfigTargetFieldName", 459 input: []byte(`{ 460 "routeLookupConfig": { 461 "grpcKeybuilders": [{ 462 "names": [{"service": "service", "method": "method"}], 463 "headers": [{"key": "k1", "names": ["v1"]}] 464 }], 465 "lookupService": "passthrough:///target", 466 "lookupServiceTimeout" : "10s", 467 "maxAge": "30s", 468 "staleAge" : "25s", 469 "cacheSizeBytes": 1000, 470 "defaultTarget": "passthrough:///default" 471 }, 472 "childPolicy": [ 473 {"cds_experimental": {"Cluster": "my-fav-cluster"}}, 474 {"unknown-policy": {"unknown-field": "unknown-value"}}, 475 {"grpclb": {}} 476 ] 477 }`), 478 wantErr: "rls: childPolicyConfigTargetFieldName field is not set in service config", 479 }, 480 { 481 desc: "child policy config validation failure", 482 input: []byte(`{ 483 "routeLookupConfig": { 484 "grpcKeybuilders": [{ 485 "names": [{"service": "service", "method": "method"}], 486 "headers": [{"key": "k1", "names": ["v1"]}] 487 }], 488 "lookupService": "passthrough:///target", 489 "lookupServiceTimeout" : "10s", 490 "maxAge": "30s", 491 "staleAge" : "25s", 492 "cacheSizeBytes": 1000, 493 "defaultTarget": "passthrough:///default" 494 }, 495 "childPolicy": [ 496 {"cds_experimental": {"Cluster": "my-fav-cluster"}}, 497 {"unknown-policy": {"unknown-field": "unknown-value"}}, 498 {"grpclb": {"childPolicy": "not-an-array"}} 499 ], 500 "childPolicyConfigTargetFieldName": "serviceName" 501 }`), 502 wantErr: "rls: childPolicy config validation failed", 503 }, 504 } 505 506 builder := rlsBB{} 507 for _, test := range tests { 508 t.Run(test.desc, func(t *testing.T) { 509 lbCfg, err := builder.ParseConfig(test.input) 510 if lbCfg != nil || !strings.Contains(fmt.Sprint(err), test.wantErr) { 511 t.Errorf("ParseConfig(%s) = {%+v, %v}, want {nil, %s}", string(test.input), lbCfg, err, test.wantErr) 512 } 513 }) 514 } 515 }