google.golang.org/grpc@v1.62.1/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 set to maxMaxAge since the value is too large in the input. 64 // - staleAge is ignore 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: 5 * time.Minute, // This is max maxAge. 92 staleAge: time.Duration(0), // StaleAge is ignore because it was higher than maxAge. 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: "without transformations", 105 input: []byte(`{ 106 "routeLookupConfig": { 107 "grpcKeybuilders": [{ 108 "names": [{"service": "service", "method": "method"}], 109 "headers": [{"key": "k1", "names": ["v1"]}] 110 }], 111 "lookupService": "target", 112 "lookupServiceTimeout" : "100s", 113 "maxAge": "60s", 114 "staleAge" : "50s", 115 "cacheSizeBytes": 1000, 116 "defaultTarget": "passthrough:///default" 117 }, 118 "routeLookupChannelServiceConfig": {"loadBalancingConfig": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}]}, 119 "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}], 120 "childPolicyConfigTargetFieldName": "serviceName" 121 }`), 122 wantCfg: &lbConfig{ 123 lookupService: "target", 124 lookupServiceTimeout: 100 * time.Second, 125 maxAge: 60 * time.Second, 126 staleAge: 50 * time.Second, 127 cacheSizeBytes: 1000, 128 defaultTarget: "passthrough:///default", 129 controlChannelServiceConfig: `{"loadBalancingConfig": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}]}`, 130 childPolicyName: "grpclb", 131 childPolicyTargetField: "serviceName", 132 childPolicyConfig: map[string]json.RawMessage{ 133 "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`), 134 "serviceName": json.RawMessage(childPolicyTargetFieldVal), 135 }, 136 }, 137 }, 138 } 139 140 builder := rlsBB{} 141 for _, test := range tests { 142 t.Run(test.desc, func(t *testing.T) { 143 lbCfg, err := builder.ParseConfig(test.input) 144 if err != nil || !testEqual(lbCfg.(*lbConfig), test.wantCfg) { 145 t.Errorf("ParseConfig(%s) = {%+v, %v}, want {%+v, nil}", string(test.input), lbCfg, err, test.wantCfg) 146 } 147 }) 148 } 149 } 150 151 // TestParseConfigErrors verifies config parsing failure scenarios. 152 func (s) TestParseConfigErrors(t *testing.T) { 153 tests := []struct { 154 desc string 155 input []byte 156 wantErr string 157 }{ 158 { 159 desc: "empty input", 160 input: nil, 161 wantErr: "rls: json unmarshal failed for service config", 162 }, 163 { 164 desc: "bad json", 165 input: []byte(`bad bad json`), 166 wantErr: "rls: json unmarshal failed for service config", 167 }, 168 { 169 desc: "bad grpcKeyBuilder", 170 input: []byte(`{ 171 "routeLookupConfig": { 172 "grpcKeybuilders": [{ 173 "names": [{"service": "service", "method": "method"}], 174 "headers": [{"key": "k1", "requiredMatch": true, "names": ["v1"]}] 175 }] 176 } 177 }`), 178 wantErr: "rls: GrpcKeyBuilder in RouteLookupConfig has required_match field set", 179 }, 180 { 181 desc: "empty lookup service", 182 input: []byte(`{ 183 "routeLookupConfig": { 184 "grpcKeybuilders": [{ 185 "names": [{"service": "service", "method": "method"}], 186 "headers": [{"key": "k1", "names": ["v1"]}] 187 }] 188 } 189 }`), 190 wantErr: "rls: empty lookup_service in route lookup config", 191 }, 192 { 193 desc: "unregistered scheme in lookup service URI", 194 input: []byte(`{ 195 "routeLookupConfig": { 196 "grpcKeybuilders": [{ 197 "names": [{"service": "service", "method": "method"}], 198 "headers": [{"key": "k1", "names": ["v1"]}] 199 }], 200 "lookupService": "badScheme:///target" 201 } 202 }`), 203 wantErr: "rls: unregistered scheme in lookup_service", 204 }, 205 { 206 desc: "invalid lookup service timeout", 207 input: []byte(`{ 208 "routeLookupConfig": { 209 "grpcKeybuilders": [{ 210 "names": [{"service": "service", "method": "method"}], 211 "headers": [{"key": "k1", "names": ["v1"]}] 212 }], 213 "lookupService": "passthrough:///target", 214 "lookupServiceTimeout" : "315576000001s" 215 } 216 }`), 217 wantErr: "google.protobuf.Duration value out of range", 218 }, 219 { 220 desc: "invalid max age", 221 input: []byte(`{ 222 "routeLookupConfig": { 223 "grpcKeybuilders": [{ 224 "names": [{"service": "service", "method": "method"}], 225 "headers": [{"key": "k1", "names": ["v1"]}] 226 }], 227 "lookupService": "passthrough:///target", 228 "lookupServiceTimeout" : "10s", 229 "maxAge" : "315576000001s" 230 } 231 }`), 232 wantErr: "google.protobuf.Duration value out of range", 233 }, 234 { 235 desc: "invalid stale age", 236 input: []byte(`{ 237 "routeLookupConfig": { 238 "grpcKeybuilders": [{ 239 "names": [{"service": "service", "method": "method"}], 240 "headers": [{"key": "k1", "names": ["v1"]}] 241 }], 242 "lookupService": "passthrough:///target", 243 "lookupServiceTimeout" : "10s", 244 "maxAge" : "10s", 245 "staleAge" : "315576000001s" 246 } 247 }`), 248 wantErr: "google.protobuf.Duration value out of range", 249 }, 250 { 251 desc: "invalid max age stale age combo", 252 input: []byte(`{ 253 "routeLookupConfig": { 254 "grpcKeybuilders": [{ 255 "names": [{"service": "service", "method": "method"}], 256 "headers": [{"key": "k1", "names": ["v1"]}] 257 }], 258 "lookupService": "passthrough:///target", 259 "lookupServiceTimeout" : "10s", 260 "staleAge" : "10s" 261 } 262 }`), 263 wantErr: "rls: stale_age is set, but max_age is not in route lookup config", 264 }, 265 { 266 desc: "cache_size_bytes field is not set", 267 input: []byte(`{ 268 "routeLookupConfig": { 269 "grpcKeybuilders": [{ 270 "names": [{"service": "service", "method": "method"}], 271 "headers": [{"key": "k1", "names": ["v1"]}] 272 }], 273 "lookupService": "passthrough:///target", 274 "lookupServiceTimeout" : "10s", 275 "maxAge": "30s", 276 "staleAge" : "25s", 277 "defaultTarget": "passthrough:///default" 278 }, 279 "childPolicyConfigTargetFieldName": "serviceName" 280 }`), 281 wantErr: "rls: cache_size_bytes must be set to a non-zero value", 282 }, 283 { 284 desc: "routeLookupChannelServiceConfig is not in service config format", 285 input: []byte(`{ 286 "routeLookupConfig": { 287 "grpcKeybuilders": [{ 288 "names": [{"service": "service", "method": "method"}], 289 "headers": [{"key": "k1", "names": ["v1"]}] 290 }], 291 "lookupService": "target", 292 "lookupServiceTimeout" : "100s", 293 "maxAge": "60s", 294 "staleAge" : "50s", 295 "cacheSizeBytes": 1000, 296 "defaultTarget": "passthrough:///default" 297 }, 298 "routeLookupChannelServiceConfig": "unknown", 299 "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}], 300 "childPolicyConfigTargetFieldName": "serviceName" 301 }`), 302 wantErr: "cannot unmarshal string into Go value of type grpc.jsonSC", 303 }, 304 { 305 desc: "routeLookupChannelServiceConfig contains unknown LB policy", 306 input: []byte(`{ 307 "routeLookupConfig": { 308 "grpcKeybuilders": [{ 309 "names": [{"service": "service", "method": "method"}], 310 "headers": [{"key": "k1", "names": ["v1"]}] 311 }], 312 "lookupService": "target", 313 "lookupServiceTimeout" : "100s", 314 "maxAge": "60s", 315 "staleAge" : "50s", 316 "cacheSizeBytes": 1000, 317 "defaultTarget": "passthrough:///default" 318 }, 319 "routeLookupChannelServiceConfig": { 320 "loadBalancingConfig": [{"not_a_balancer1": {} }, {"not_a_balancer2": {}}] 321 }, 322 "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}], 323 "childPolicyConfigTargetFieldName": "serviceName" 324 }`), 325 wantErr: "invalid loadBalancingConfig: no supported policies found", 326 }, 327 { 328 desc: "no child policy", 329 input: []byte(`{ 330 "routeLookupConfig": { 331 "grpcKeybuilders": [{ 332 "names": [{"service": "service", "method": "method"}], 333 "headers": [{"key": "k1", "names": ["v1"]}] 334 }], 335 "lookupService": "passthrough:///target", 336 "lookupServiceTimeout" : "10s", 337 "maxAge": "30s", 338 "staleAge" : "25s", 339 "cacheSizeBytes": 1000, 340 "defaultTarget": "passthrough:///default" 341 }, 342 "childPolicyConfigTargetFieldName": "serviceName" 343 }`), 344 wantErr: "rls: invalid childPolicy config: no supported policies found", 345 }, 346 { 347 desc: "no known child policy", 348 input: []byte(`{ 349 "routeLookupConfig": { 350 "grpcKeybuilders": [{ 351 "names": [{"service": "service", "method": "method"}], 352 "headers": [{"key": "k1", "names": ["v1"]}] 353 }], 354 "lookupService": "passthrough:///target", 355 "lookupServiceTimeout" : "10s", 356 "maxAge": "30s", 357 "staleAge" : "25s", 358 "cacheSizeBytes": 1000, 359 "defaultTarget": "passthrough:///default" 360 }, 361 "childPolicy": [ 362 {"cds_experimental": {"Cluster": "my-fav-cluster"}}, 363 {"unknown-policy": {"unknown-field": "unknown-value"}} 364 ], 365 "childPolicyConfigTargetFieldName": "serviceName" 366 }`), 367 wantErr: "rls: invalid childPolicy config: no supported policies found", 368 }, 369 { 370 desc: "invalid child policy config - more than one entry in map", 371 input: []byte(`{ 372 "routeLookupConfig": { 373 "grpcKeybuilders": [{ 374 "names": [{"service": "service", "method": "method"}], 375 "headers": [{"key": "k1", "names": ["v1"]}] 376 }], 377 "lookupService": "passthrough:///target", 378 "lookupServiceTimeout" : "10s", 379 "maxAge": "30s", 380 "staleAge" : "25s", 381 "cacheSizeBytes": 1000, 382 "defaultTarget": "passthrough:///default" 383 }, 384 "childPolicy": [ 385 { 386 "cds_experimental": {"Cluster": "my-fav-cluster"}, 387 "unknown-policy": {"unknown-field": "unknown-value"} 388 } 389 ], 390 "childPolicyConfigTargetFieldName": "serviceName" 391 }`), 392 wantErr: "does not contain exactly 1 policy/config pair", 393 }, 394 { 395 desc: "no childPolicyConfigTargetFieldName", 396 input: []byte(`{ 397 "routeLookupConfig": { 398 "grpcKeybuilders": [{ 399 "names": [{"service": "service", "method": "method"}], 400 "headers": [{"key": "k1", "names": ["v1"]}] 401 }], 402 "lookupService": "passthrough:///target", 403 "lookupServiceTimeout" : "10s", 404 "maxAge": "30s", 405 "staleAge" : "25s", 406 "cacheSizeBytes": 1000, 407 "defaultTarget": "passthrough:///default" 408 }, 409 "childPolicy": [ 410 {"cds_experimental": {"Cluster": "my-fav-cluster"}}, 411 {"unknown-policy": {"unknown-field": "unknown-value"}}, 412 {"grpclb": {}} 413 ] 414 }`), 415 wantErr: "rls: childPolicyConfigTargetFieldName field is not set in service config", 416 }, 417 { 418 desc: "child policy config validation failure", 419 input: []byte(`{ 420 "routeLookupConfig": { 421 "grpcKeybuilders": [{ 422 "names": [{"service": "service", "method": "method"}], 423 "headers": [{"key": "k1", "names": ["v1"]}] 424 }], 425 "lookupService": "passthrough:///target", 426 "lookupServiceTimeout" : "10s", 427 "maxAge": "30s", 428 "staleAge" : "25s", 429 "cacheSizeBytes": 1000, 430 "defaultTarget": "passthrough:///default" 431 }, 432 "childPolicy": [ 433 {"cds_experimental": {"Cluster": "my-fav-cluster"}}, 434 {"unknown-policy": {"unknown-field": "unknown-value"}}, 435 {"grpclb": {"childPolicy": "not-an-array"}} 436 ], 437 "childPolicyConfigTargetFieldName": "serviceName" 438 }`), 439 wantErr: "rls: childPolicy config validation failed", 440 }, 441 } 442 443 builder := rlsBB{} 444 for _, test := range tests { 445 t.Run(test.desc, func(t *testing.T) { 446 lbCfg, err := builder.ParseConfig(test.input) 447 if lbCfg != nil || !strings.Contains(fmt.Sprint(err), test.wantErr) { 448 t.Errorf("ParseConfig(%s) = {%+v, %v}, want {nil, %s}", string(test.input), lbCfg, err, test.wantErr) 449 } 450 }) 451 } 452 }