/* * * 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 actionConfig 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 routeConfig 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 []routeConfig actions map[string]actionConfig } // 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]actionConfig } func (jc lbConfigJSON) toLBConfig() *lbConfig { var ret lbConfig for _, r := range jc.Route { var tempR routeConfig 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 { 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 }