mirror of https://github.com/grpc/grpc-go.git
235 lines
7.2 KiB
Go
235 lines
7.2 KiB
Go
/*
|
|
*
|
|
* 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 keys provides functionality required to build RLS request keys.
|
|
package keys
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
rlspb "google.golang.org/grpc/internal/proto/grpc_lookup_v1"
|
|
"google.golang.org/grpc/metadata"
|
|
)
|
|
|
|
// BuilderMap provides a mapping from a request path to the key builder to be
|
|
// used for that path.
|
|
// The BuilderMap is constructed by parsing the RouteLookupConfig received by
|
|
// the RLS balancer as part of its ServiceConfig, and is used by the picker in
|
|
// the data path to build the RLS keys to be used for a given request.
|
|
type BuilderMap map[string]builder
|
|
|
|
// MakeBuilderMap parses the provided RouteLookupConfig proto and returns a map
|
|
// from paths to key builders.
|
|
//
|
|
// The following conditions are validated, and an error is returned if any of
|
|
// them is not met:
|
|
// grpc_keybuilders field
|
|
// * must have at least one entry
|
|
// * must not have two entries with the same Name
|
|
// * must not have any entry with a Name with the service field unset or empty
|
|
// * must not have any entries without a Name
|
|
// * must not have a headers entry that has required_match set
|
|
// * must not have two headers entries with the same key within one entry
|
|
func MakeBuilderMap(cfg *rlspb.RouteLookupConfig) (BuilderMap, error) {
|
|
kbs := cfg.GetGrpcKeybuilders()
|
|
if len(kbs) == 0 {
|
|
return nil, errors.New("rls: RouteLookupConfig does not contain any GrpcKeyBuilder")
|
|
}
|
|
|
|
bm := make(map[string]builder)
|
|
for _, kb := range kbs {
|
|
var matchers []matcher
|
|
seenKeys := make(map[string]bool)
|
|
for _, h := range kb.GetHeaders() {
|
|
if h.GetRequiredMatch() {
|
|
return nil, fmt.Errorf("rls: GrpcKeyBuilder in RouteLookupConfig has required_match field set {%+v}", kbs)
|
|
}
|
|
key := h.GetKey()
|
|
if seenKeys[key] {
|
|
return nil, fmt.Errorf("rls: GrpcKeyBuilder in RouteLookupConfig contains repeated Key field in headers {%+v}", kbs)
|
|
}
|
|
seenKeys[key] = true
|
|
matchers = append(matchers, matcher{key: h.GetKey(), names: h.GetNames()})
|
|
}
|
|
b := builder{matchers: matchers}
|
|
|
|
names := kb.GetNames()
|
|
if len(names) == 0 {
|
|
return nil, fmt.Errorf("rls: GrpcKeyBuilder in RouteLookupConfig does not contain any Name {%+v}", kbs)
|
|
}
|
|
for _, name := range names {
|
|
if name.GetService() == "" {
|
|
return nil, fmt.Errorf("rls: GrpcKeyBuilder in RouteLookupConfig contains a Name field with no Service {%+v}", kbs)
|
|
}
|
|
if strings.Contains(name.GetMethod(), `/`) {
|
|
return nil, fmt.Errorf("rls: GrpcKeyBuilder in RouteLookupConfig contains a method with a slash {%+v}", kbs)
|
|
}
|
|
path := "/" + name.GetService() + "/" + name.GetMethod()
|
|
if _, ok := bm[path]; ok {
|
|
return nil, fmt.Errorf("rls: GrpcKeyBuilder in RouteLookupConfig contains repeated Name field {%+v}", kbs)
|
|
}
|
|
bm[path] = b
|
|
}
|
|
}
|
|
return bm, nil
|
|
}
|
|
|
|
// KeyMap represents the RLS keys to be used for a request.
|
|
type KeyMap struct {
|
|
// Map is the representation of an RLS key as a Go map. This is used when
|
|
// an actual RLS request is to be sent out on the wire, since the
|
|
// RouteLookupRequest proto expects a Go map.
|
|
Map map[string]string
|
|
// Str is the representation of an RLS key as a string, sorted by keys.
|
|
// Since the RLS keys are part of the cache key in the request cache
|
|
// maintained by the RLS balancer, and Go maps cannot be used as keys for
|
|
// Go maps (the cache is implemented as a map), we need a stringified
|
|
// version of it.
|
|
Str string
|
|
}
|
|
|
|
// RLSKey builds the RLS keys to be used for the given request, identified by
|
|
// the request path and the request headers stored in metadata.
|
|
func (bm BuilderMap) RLSKey(md metadata.MD, path string) KeyMap {
|
|
b, ok := bm[path]
|
|
if !ok {
|
|
i := strings.LastIndex(path, "/")
|
|
b, ok = bm[path[:i+1]]
|
|
if !ok {
|
|
return KeyMap{}
|
|
}
|
|
}
|
|
return b.keys(md)
|
|
}
|
|
|
|
// Equal reports whether bm and am represent equivalent BuilderMaps.
|
|
func (bm BuilderMap) Equal(am BuilderMap) bool {
|
|
if (bm == nil) != (am == nil) {
|
|
return false
|
|
}
|
|
if len(bm) != len(am) {
|
|
return false
|
|
}
|
|
|
|
for key, bBuilder := range bm {
|
|
aBuilder, ok := am[key]
|
|
if !ok {
|
|
return false
|
|
}
|
|
if !bBuilder.Equal(aBuilder) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// builder provides the actual functionality of building RLS keys. These are
|
|
// stored in the BuilderMap.
|
|
// While processing a pick, the picker looks in the BuilderMap for the
|
|
// appropriate builder to be used for the given RPC. For each of the matchers
|
|
// in the found builder, we iterate over the list of request headers (available
|
|
// as metadata in the context). Once a header matches one of the names in the
|
|
// matcher, we set the value of the header in the keyMap (with the key being
|
|
// the one found in the matcher) and move on to the next matcher. If no
|
|
// KeyBuilder was found in the map, or no header match was found, an empty
|
|
// keyMap is returned.
|
|
type builder struct {
|
|
matchers []matcher
|
|
}
|
|
|
|
// Equal reports whether b and a represent equivalent key builders.
|
|
func (b builder) Equal(a builder) bool {
|
|
if (b.matchers == nil) != (a.matchers == nil) {
|
|
return false
|
|
}
|
|
if len(b.matchers) != len(a.matchers) {
|
|
return false
|
|
}
|
|
// Protobuf serialization maintains the order of repeated fields. Matchers
|
|
// are specified as a repeated field inside the KeyBuilder proto. If the
|
|
// order changes, it means that the order in the protobuf changed. We report
|
|
// this case as not being equal even though the builders could possible be
|
|
// functionally equal.
|
|
for i, bMatcher := range b.matchers {
|
|
aMatcher := a.matchers[i]
|
|
if !bMatcher.Equal(aMatcher) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// matcher helps extract a key from request headers based on a given name.
|
|
type matcher struct {
|
|
// The key used in the keyMap sent as part of the RLS request.
|
|
key string
|
|
// List of header names which can supply the value for this key.
|
|
names []string
|
|
}
|
|
|
|
// Equal reports if m and are are equivalent matchers.
|
|
func (m matcher) Equal(a matcher) bool {
|
|
if m.key != a.key {
|
|
return false
|
|
}
|
|
if (m.names == nil) != (a.names == nil) {
|
|
return false
|
|
}
|
|
if len(m.names) != len(a.names) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(m.names); i++ {
|
|
if m.names[i] != a.names[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (b builder) keys(md metadata.MD) KeyMap {
|
|
kvMap := make(map[string]string)
|
|
for _, m := range b.matchers {
|
|
for _, name := range m.names {
|
|
if vals := md.Get(name); vals != nil {
|
|
kvMap[m.key] = strings.Join(vals, ",")
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return KeyMap{Map: kvMap, Str: mapToString(kvMap)}
|
|
}
|
|
|
|
func mapToString(kv map[string]string) string {
|
|
keys := make([]string, 0, len(kv))
|
|
for k := range kv {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
var sb strings.Builder
|
|
for i, k := range keys {
|
|
if i != 0 {
|
|
fmt.Fprint(&sb, ",")
|
|
}
|
|
fmt.Fprintf(&sb, "%s=%s", k, kv[k])
|
|
}
|
|
return sb.String()
|
|
}
|