123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- /*
- *
- * Copyright 2020 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
- package rls
- import (
- "encoding/json"
- "fmt"
- "strings"
- "testing"
- "time"
- _ "google.golang.org/grpc/balancer/grpclb" // grpclb for config parsing.
- _ "google.golang.org/grpc/internal/resolver/passthrough" // passthrough resolver.
- )
- // testEqual reports whether the lbCfgs a and b are equal. This is to be used
- // only from tests. This ignores the keyBuilderMap field because its internals
- // are not exported, and hence not possible to specify in the want section of
- // the test. This is fine because we already have tests to make sure that the
- // keyBuilder is parsed properly from the service config.
- func testEqual(a, b *lbConfig) bool {
- return a.lookupService == b.lookupService &&
- a.lookupServiceTimeout == b.lookupServiceTimeout &&
- a.maxAge == b.maxAge &&
- a.staleAge == b.staleAge &&
- a.cacheSizeBytes == b.cacheSizeBytes &&
- a.defaultTarget == b.defaultTarget &&
- a.controlChannelServiceConfig == b.controlChannelServiceConfig &&
- a.childPolicyName == b.childPolicyName &&
- a.childPolicyTargetField == b.childPolicyTargetField &&
- childPolicyConfigEqual(a.childPolicyConfig, b.childPolicyConfig)
- }
- // TestParseConfig verifies successful config parsing scenarios.
- func (s) TestParseConfig(t *testing.T) {
- childPolicyTargetFieldVal, _ := json.Marshal(dummyChildPolicyTarget)
- tests := []struct {
- desc string
- input []byte
- wantCfg *lbConfig
- }{
- {
- // This input validates a few cases:
- // - A top-level unknown field should not fail.
- // - An unknown field in routeLookupConfig proto should not fail.
- // - lookupServiceTimeout is set to its default value, since it is not specified in the input.
- // - maxAge is set to maxMaxAge since the value is too large in the input.
- // - staleAge is ignore because it is higher than maxAge in the input.
- // - cacheSizeBytes is greater than the hard upper limit of 5MB
- desc: "with transformations 1",
- input: []byte(`{
- "top-level-unknown-field": "unknown-value",
- "routeLookupConfig": {
- "unknown-field": "unknown-value",
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": ":///target",
- "maxAge" : "500s",
- "staleAge": "600s",
- "cacheSizeBytes": 100000000,
- "defaultTarget": "passthrough:///default"
- },
- "childPolicy": [
- {"cds_experimental": {"Cluster": "my-fav-cluster"}},
- {"unknown-policy": {"unknown-field": "unknown-value"}},
- {"grpclb": {"childPolicy": [{"pickfirst": {}}]}}
- ],
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantCfg: &lbConfig{
- lookupService: ":///target",
- lookupServiceTimeout: 10 * time.Second, // This is the default value.
- maxAge: 5 * time.Minute, // This is max maxAge.
- staleAge: time.Duration(0), // StaleAge is ignore because it was higher than maxAge.
- cacheSizeBytes: maxCacheSize,
- defaultTarget: "passthrough:///default",
- childPolicyName: "grpclb",
- childPolicyTargetField: "serviceName",
- childPolicyConfig: map[string]json.RawMessage{
- "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`),
- "serviceName": json.RawMessage(childPolicyTargetFieldVal),
- },
- },
- },
- {
- desc: "without transformations",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "target",
- "lookupServiceTimeout" : "100s",
- "maxAge": "60s",
- "staleAge" : "50s",
- "cacheSizeBytes": 1000,
- "defaultTarget": "passthrough:///default"
- },
- "routeLookupChannelServiceConfig": {"loadBalancingConfig": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}]},
- "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantCfg: &lbConfig{
- lookupService: "target",
- lookupServiceTimeout: 100 * time.Second,
- maxAge: 60 * time.Second,
- staleAge: 50 * time.Second,
- cacheSizeBytes: 1000,
- defaultTarget: "passthrough:///default",
- controlChannelServiceConfig: `{"loadBalancingConfig": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}]}`,
- childPolicyName: "grpclb",
- childPolicyTargetField: "serviceName",
- childPolicyConfig: map[string]json.RawMessage{
- "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`),
- "serviceName": json.RawMessage(childPolicyTargetFieldVal),
- },
- },
- },
- }
- builder := rlsBB{}
- for _, test := range tests {
- t.Run(test.desc, func(t *testing.T) {
- lbCfg, err := builder.ParseConfig(test.input)
- if err != nil || !testEqual(lbCfg.(*lbConfig), test.wantCfg) {
- t.Errorf("ParseConfig(%s) = {%+v, %v}, want {%+v, nil}", string(test.input), lbCfg, err, test.wantCfg)
- }
- })
- }
- }
- // TestParseConfigErrors verifies config parsing failure scenarios.
- func (s) TestParseConfigErrors(t *testing.T) {
- tests := []struct {
- desc string
- input []byte
- wantErr string
- }{
- {
- desc: "empty input",
- input: nil,
- wantErr: "rls: json unmarshal failed for service config",
- },
- {
- desc: "bad json",
- input: []byte(`bad bad json`),
- wantErr: "rls: json unmarshal failed for service config",
- },
- {
- desc: "bad grpcKeyBuilder",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "requiredMatch": true, "names": ["v1"]}]
- }]
- }
- }`),
- wantErr: "rls: GrpcKeyBuilder in RouteLookupConfig has required_match field set",
- },
- {
- desc: "empty lookup service",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }]
- }
- }`),
- wantErr: "rls: empty lookup_service in route lookup config",
- },
- {
- desc: "unregistered scheme in lookup service URI",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "badScheme:///target"
- }
- }`),
- wantErr: "rls: unregistered scheme in lookup_service",
- },
- {
- desc: "invalid lookup service timeout",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "315576000001s"
- }
- }`),
- wantErr: "google.protobuf.Duration value out of range",
- },
- {
- desc: "invalid max age",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "maxAge" : "315576000001s"
- }
- }`),
- wantErr: "google.protobuf.Duration value out of range",
- },
- {
- desc: "invalid stale age",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "maxAge" : "10s",
- "staleAge" : "315576000001s"
- }
- }`),
- wantErr: "google.protobuf.Duration value out of range",
- },
- {
- desc: "invalid max age stale age combo",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "staleAge" : "10s"
- }
- }`),
- wantErr: "rls: stale_age is set, but max_age is not in route lookup config",
- },
- {
- desc: "cache_size_bytes field is not set",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "maxAge": "30s",
- "staleAge" : "25s",
- "defaultTarget": "passthrough:///default"
- },
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantErr: "rls: cache_size_bytes must be set to a non-zero value",
- },
- {
- desc: "routeLookupChannelServiceConfig is not in service config format",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "target",
- "lookupServiceTimeout" : "100s",
- "maxAge": "60s",
- "staleAge" : "50s",
- "cacheSizeBytes": 1000,
- "defaultTarget": "passthrough:///default"
- },
- "routeLookupChannelServiceConfig": "unknown",
- "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantErr: "cannot unmarshal string into Go value of type grpc.jsonSC",
- },
- {
- desc: "routeLookupChannelServiceConfig contains unknown LB policy",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "target",
- "lookupServiceTimeout" : "100s",
- "maxAge": "60s",
- "staleAge" : "50s",
- "cacheSizeBytes": 1000,
- "defaultTarget": "passthrough:///default"
- },
- "routeLookupChannelServiceConfig": {
- "loadBalancingConfig": [{"not_a_balancer1": {} }, {"not_a_balancer2": {}}]
- },
- "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantErr: "invalid loadBalancingConfig: no supported policies found",
- },
- {
- desc: "no child policy",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "maxAge": "30s",
- "staleAge" : "25s",
- "cacheSizeBytes": 1000,
- "defaultTarget": "passthrough:///default"
- },
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantErr: "rls: invalid childPolicy config: no supported policies found",
- },
- {
- desc: "no known child policy",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "maxAge": "30s",
- "staleAge" : "25s",
- "cacheSizeBytes": 1000,
- "defaultTarget": "passthrough:///default"
- },
- "childPolicy": [
- {"cds_experimental": {"Cluster": "my-fav-cluster"}},
- {"unknown-policy": {"unknown-field": "unknown-value"}}
- ],
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantErr: "rls: invalid childPolicy config: no supported policies found",
- },
- {
- desc: "invalid child policy config - more than one entry in map",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "maxAge": "30s",
- "staleAge" : "25s",
- "cacheSizeBytes": 1000,
- "defaultTarget": "passthrough:///default"
- },
- "childPolicy": [
- {
- "cds_experimental": {"Cluster": "my-fav-cluster"},
- "unknown-policy": {"unknown-field": "unknown-value"}
- }
- ],
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantErr: "does not contain exactly 1 policy/config pair",
- },
- {
- desc: "no childPolicyConfigTargetFieldName",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "maxAge": "30s",
- "staleAge" : "25s",
- "cacheSizeBytes": 1000,
- "defaultTarget": "passthrough:///default"
- },
- "childPolicy": [
- {"cds_experimental": {"Cluster": "my-fav-cluster"}},
- {"unknown-policy": {"unknown-field": "unknown-value"}},
- {"grpclb": {}}
- ]
- }`),
- wantErr: "rls: childPolicyConfigTargetFieldName field is not set in service config",
- },
- {
- desc: "child policy config validation failure",
- input: []byte(`{
- "routeLookupConfig": {
- "grpcKeybuilders": [{
- "names": [{"service": "service", "method": "method"}],
- "headers": [{"key": "k1", "names": ["v1"]}]
- }],
- "lookupService": "passthrough:///target",
- "lookupServiceTimeout" : "10s",
- "maxAge": "30s",
- "staleAge" : "25s",
- "cacheSizeBytes": 1000,
- "defaultTarget": "passthrough:///default"
- },
- "childPolicy": [
- {"cds_experimental": {"Cluster": "my-fav-cluster"}},
- {"unknown-policy": {"unknown-field": "unknown-value"}},
- {"grpclb": {"childPolicy": "not-an-array"}}
- ],
- "childPolicyConfigTargetFieldName": "serviceName"
- }`),
- wantErr: "rls: childPolicy config validation failed",
- },
- }
- builder := rlsBB{}
- for _, test := range tests {
- t.Run(test.desc, func(t *testing.T) {
- lbCfg, err := builder.ParseConfig(test.input)
- if lbCfg != nil || !strings.Contains(fmt.Sprint(err), test.wantErr) {
- t.Errorf("ParseConfig(%s) = {%+v, %v}, want {nil, %s}", string(test.input), lbCfg, err, test.wantErr)
- }
- })
- }
- }
|