blob: 6daea224a14d9c8ab192dcd03bef20c7324e1018 [file] [log] [blame]
/*
*
* 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 xdsrouting
import (
"encoding/json"
"fmt"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
"google.golang.org/grpc/serviceconfig"
xdsclient "google.golang.org/grpc/xds/internal/client"
)
type action struct {
// ChildPolicy is the child policy and it's config.
ChildPolicy *internalserviceconfig.BalancerConfig
}
type int64Range struct {
start, end int64
}
type headerMatcher struct {
name string
// matchSpecifiers
invertMatch bool
// At most one of the following has non-default value.
exactMatch, regexMatch, prefixMatch, suffixMatch string
rangeMatch *int64Range
presentMatch bool
}
type route struct {
// Path, Prefix and Regex can have at most one set. This is guaranteed by
// config parsing.
path, prefix, regex string
headers []headerMatcher
fraction *uint32
// Action is the name from the action list.
action string
}
// lbConfig is the balancer config for xds routing policy.
type lbConfig struct {
serviceconfig.LoadBalancingConfig
routes []route
actions map[string]action
}
// The following structs with `JSON` in name are temporary structs to unmarshal
// json into. The fields will be read into lbConfig, to be used by the balancer.
// routeJSON is temporary struct for json unmarshal.
type routeJSON struct {
// Path, Prefix and Regex can have at most one non-nil.
Path, Prefix, Regex *string
// Zero or more header matchers.
Headers []*xdsclient.HeaderMatcher
MatchFraction *wrapperspb.UInt32Value
// Action is the name from the action list.
Action string
}
// lbConfigJSON is temporary struct for json unmarshal.
type lbConfigJSON struct {
Route []routeJSON
Action map[string]action
}
func (jc lbConfigJSON) toLBConfig() *lbConfig {
var ret lbConfig
for _, r := range jc.Route {
var tempR route
switch {
case r.Path != nil:
tempR.path = *r.Path
case r.Prefix != nil:
tempR.prefix = *r.Prefix
case r.Regex != nil:
tempR.regex = *r.Regex
}
for _, h := range r.Headers {
if h.RangeMatch != nil {
fmt.Println("range not nil", *h.RangeMatch)
}
var tempHeader headerMatcher
switch {
case h.ExactMatch != nil:
tempHeader.exactMatch = *h.ExactMatch
case h.RegexMatch != nil:
tempHeader.regexMatch = *h.RegexMatch
case h.PrefixMatch != nil:
tempHeader.prefixMatch = *h.PrefixMatch
case h.SuffixMatch != nil:
tempHeader.suffixMatch = *h.SuffixMatch
case h.RangeMatch != nil:
tempHeader.rangeMatch = &int64Range{
start: h.RangeMatch.Start,
end: h.RangeMatch.End,
}
case h.PresentMatch != nil:
tempHeader.presentMatch = *h.PresentMatch
}
tempHeader.name = h.Name
if h.InvertMatch != nil {
tempHeader.invertMatch = *h.InvertMatch
}
tempR.headers = append(tempR.headers, tempHeader)
}
if r.MatchFraction != nil {
tempR.fraction = &r.MatchFraction.Value
}
tempR.action = r.Action
ret.routes = append(ret.routes, tempR)
}
ret.actions = jc.Action
return &ret
}
func parseConfig(c json.RawMessage) (*lbConfig, error) {
var tempConfig lbConfigJSON
if err := json.Unmarshal(c, &tempConfig); err != nil {
return nil, err
}
// For each route:
// - at most one of path/prefix/regex.
// - action is in action list.
allRouteActions := make(map[string]bool)
for _, r := range tempConfig.Route {
var oneOfCount int
if r.Path != nil {
oneOfCount++
}
if r.Prefix != nil {
oneOfCount++
}
if r.Regex != nil {
oneOfCount++
}
if oneOfCount != 1 {
return nil, fmt.Errorf("%d (not exactly one) of path/prefix/regex is set in route %+v", oneOfCount, r)
}
for _, h := range r.Headers {
var oneOfCountH int
if h.ExactMatch != nil {
oneOfCountH++
}
if h.RegexMatch != nil {
oneOfCountH++
}
if h.PrefixMatch != nil {
oneOfCountH++
}
if h.SuffixMatch != nil {
oneOfCountH++
}
if h.RangeMatch != nil {
oneOfCountH++
}
if h.PresentMatch != nil {
oneOfCountH++
}
if oneOfCountH != 1 {
return nil, fmt.Errorf("%d (not exactly one) of header matcher specifier is set in route %+v", oneOfCountH, h)
}
}
if _, ok := tempConfig.Action[r.Action]; !ok {
return nil, fmt.Errorf("action %q from route %+v is not found in action list", r.Action, r)
}
allRouteActions[r.Action] = true
}
// Verify that actions are used by at least one route.
for n := range tempConfig.Action {
if _, ok := allRouteActions[n]; !ok {
return nil, fmt.Errorf("action %q is not used by any route", n)
}
}
return tempConfig.toLBConfig(), nil
}