service config: add default method config support (#3684)

This commit is contained in:
Aliaksandr Mianzhynski 2020-07-07 03:56:02 +03:00 committed by GitHub
parent 3de8449f85
commit 4258d12073
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 143 additions and 20 deletions

View File

@ -860,9 +860,10 @@ func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool {
// GetMethodConfig gets the method config of the input method. // GetMethodConfig gets the method config of the input method.
// If there's an exact match for input method (i.e. /service/method), we return // If there's an exact match for input method (i.e. /service/method), we return
// the corresponding MethodConfig. // the corresponding MethodConfig.
// If there isn't an exact match for the input method, we look for the default config // If there isn't an exact match for the input method, we look for the service's default
// under the service (i.e /service/). If there is a default MethodConfig for // config under the service (i.e /service/) and then for the default for all services (empty string).
// the service, we return it. //
// If there is a default MethodConfig for the service, we return it.
// Otherwise, we return an empty MethodConfig. // Otherwise, we return an empty MethodConfig.
func (cc *ClientConn) GetMethodConfig(method string) MethodConfig { func (cc *ClientConn) GetMethodConfig(method string) MethodConfig {
// TODO: Avoid the locking here. // TODO: Avoid the locking here.
@ -871,12 +872,14 @@ func (cc *ClientConn) GetMethodConfig(method string) MethodConfig {
if cc.sc == nil { if cc.sc == nil {
return MethodConfig{} return MethodConfig{}
} }
m, ok := cc.sc.Methods[method] if m, ok := cc.sc.Methods[method]; ok {
if !ok { return m
i := strings.LastIndex(method, "/")
m = cc.sc.Methods[method[:i+1]]
} }
return m i := strings.LastIndex(method, "/")
if m, ok := cc.sc.Methods[method[:i+1]]; ok {
return m
}
return cc.sc.Methods[""]
} }
func (cc *ClientConn) healthCheckConfig() *healthCheckConfig { func (cc *ClientConn) healthCheckConfig() *healthCheckConfig {

View File

@ -776,6 +776,29 @@ func (s) TestDisableServiceConfigOption(t *testing.T) {
} }
} }
func (s) TestMethodConfigDefaultService(t *testing.T) {
addr := "nonexist:///non.existent"
cc, err := Dial(addr, WithInsecure(), WithDefaultServiceConfig(`{
"methodConfig": [{
"name": [
{
"service": ""
}
],
"waitForReady": true
}]
}`))
if err != nil {
t.Fatalf("Dial(%s, _) = _, %v, want _, <nil>", addr, err)
}
defer cc.Close()
m := cc.GetMethodConfig("/foo/Bar")
if m.WaitForReady == nil {
t.Fatalf("want: method (%q) config to fallback to the default service", "/foo/Bar")
}
}
func (s) TestGetClientConnTarget(t *testing.T) { func (s) TestGetClientConnTarget(t *testing.T) {
addr := "nonexist:///non.existent" addr := "nonexist:///non.existent"
cc, err := Dial(addr, WithInsecure()) cc, err := Dial(addr, WithInsecure())

View File

@ -20,6 +20,7 @@ package grpc
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
@ -224,19 +225,27 @@ func parseDuration(s *string) (*time.Duration, error) {
} }
type jsonName struct { type jsonName struct {
Service *string Service string
Method *string Method string
} }
func (j jsonName) generatePath() (string, bool) { var (
if j.Service == nil { errDuplicatedName = errors.New("duplicated name")
return "", false errEmptyServiceNonEmptyMethod = errors.New("cannot combine empty 'service' and non-empty 'method'")
)
func (j jsonName) generatePath() (string, error) {
if j.Service == "" {
if j.Method != "" {
return "", errEmptyServiceNonEmptyMethod
}
return "", nil
} }
res := "/" + *j.Service + "/" res := "/" + j.Service + "/"
if j.Method != nil { if j.Method != "" {
res += *j.Method res += j.Method
} }
return res, true return res, nil
} }
// TODO(lyuxuan): delete this struct after cleaning up old service config implementation. // TODO(lyuxuan): delete this struct after cleaning up old service config implementation.
@ -288,6 +297,8 @@ func parseServiceConfig(js string) *serviceconfig.ParseResult {
if rsc.MethodConfig == nil { if rsc.MethodConfig == nil {
return &serviceconfig.ParseResult{Config: &sc} return &serviceconfig.ParseResult{Config: &sc}
} }
paths := map[string]struct{}{}
for _, m := range *rsc.MethodConfig { for _, m := range *rsc.MethodConfig {
if m.Name == nil { if m.Name == nil {
continue continue
@ -320,10 +331,20 @@ func parseServiceConfig(js string) *serviceconfig.ParseResult {
mc.MaxRespSize = newInt(int(*m.MaxResponseMessageBytes)) mc.MaxRespSize = newInt(int(*m.MaxResponseMessageBytes))
} }
} }
for _, n := range *m.Name { for i, n := range *m.Name {
if path, valid := n.generatePath(); valid { path, err := n.generatePath()
sc.Methods[path] = mc if err != nil {
logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to methodConfig[%d]: %v", js, i, err)
return &serviceconfig.ParseResult{Err: err}
} }
if _, ok := paths[path]; ok {
err = errDuplicatedName
logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to methodConfig[%d]: %v", js, i, err)
return &serviceconfig.ParseResult{Err: err}
}
paths[path] = struct{}{}
sc.Methods[path] = mc
} }
} }

View File

@ -372,6 +372,82 @@ func (s) TestParseMsgSize(t *testing.T) {
runParseTests(t, testcases) runParseTests(t, testcases)
} }
func (s) TestParseDefaultMethodConfig(t *testing.T) {
dc := &ServiceConfig{
Methods: map[string]MethodConfig{
"": {WaitForReady: newBool(true)},
},
}
runParseTests(t, []parseTestCase{
{
`{
"methodConfig": [{
"name": [{}],
"waitForReady": true
}]
}`,
dc,
false,
},
{
`{
"methodConfig": [{
"name": [{"service": null}],
"waitForReady": true
}]
}`,
dc,
false,
},
{
`{
"methodConfig": [{
"name": [{"service": ""}],
"waitForReady": true
}]
}`,
dc,
false,
},
{
`{
"methodConfig": [{
"name": [{"method": "Bar"}],
"waitForReady": true
}]
}`,
nil,
true,
},
{
`{
"methodConfig": [{
"name": [{"service": "", "method": "Bar"}],
"waitForReady": true
}]
}`,
nil,
true,
},
})
}
func (s) TestParseMethodConfigDuplicatedName(t *testing.T) {
runParseTests(t, []parseTestCase{
{
`{
"methodConfig": [{
"name": [
{"service": "foo"},
{"service": "foo"}
],
"waitForReady": true
}]
}`, nil, true,
},
})
}
func (s) TestParseDuration(t *testing.T) { func (s) TestParseDuration(t *testing.T) {
testCases := []struct { testCases := []struct {