google.golang.org/grpc@v1.72.2/balancer/rls/config.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package rls 20 21 import ( 22 "bytes" 23 "encoding/json" 24 "fmt" 25 "net/url" 26 "time" 27 28 "google.golang.org/grpc/balancer" 29 "google.golang.org/grpc/balancer/rls/internal/keys" 30 "google.golang.org/grpc/internal" 31 "google.golang.org/grpc/internal/pretty" 32 rlspb "google.golang.org/grpc/internal/proto/grpc_lookup_v1" 33 "google.golang.org/grpc/resolver" 34 "google.golang.org/grpc/serviceconfig" 35 "google.golang.org/protobuf/encoding/protojson" 36 "google.golang.org/protobuf/types/known/durationpb" 37 ) 38 39 const ( 40 // Default max_age if not specified (or greater than this value) in the 41 // service config. 42 maxMaxAge = 5 * time.Minute 43 // Upper limit for cache_size since we don't fully trust the service config. 44 maxCacheSize = 5 * 1024 * 1024 * 8 // 5MB in bytes 45 // Default lookup_service_timeout if not specified in the service config. 46 defaultLookupServiceTimeout = 10 * time.Second 47 // Default value for targetNameField in the child policy config during 48 // service config validation. 49 dummyChildPolicyTarget = "target_name_to_be_filled_in_later" 50 ) 51 52 // lbConfig is the internal representation of the RLS LB policy's config. 53 type lbConfig struct { 54 serviceconfig.LoadBalancingConfig 55 56 cacheSizeBytes int64 // Keep this field 64-bit aligned. 57 kbMap keys.BuilderMap 58 lookupService string 59 lookupServiceTimeout time.Duration 60 maxAge time.Duration 61 staleAge time.Duration 62 defaultTarget string 63 64 childPolicyName string 65 childPolicyConfig map[string]json.RawMessage 66 childPolicyTargetField string 67 controlChannelServiceConfig string 68 } 69 70 func (lbCfg *lbConfig) Equal(other *lbConfig) bool { 71 return lbCfg.kbMap.Equal(other.kbMap) && 72 lbCfg.lookupService == other.lookupService && 73 lbCfg.lookupServiceTimeout == other.lookupServiceTimeout && 74 lbCfg.maxAge == other.maxAge && 75 lbCfg.staleAge == other.staleAge && 76 lbCfg.cacheSizeBytes == other.cacheSizeBytes && 77 lbCfg.defaultTarget == other.defaultTarget && 78 lbCfg.childPolicyName == other.childPolicyName && 79 lbCfg.childPolicyTargetField == other.childPolicyTargetField && 80 lbCfg.controlChannelServiceConfig == other.controlChannelServiceConfig && 81 childPolicyConfigEqual(lbCfg.childPolicyConfig, other.childPolicyConfig) 82 } 83 84 func childPolicyConfigEqual(a, b map[string]json.RawMessage) bool { 85 if (b == nil) != (a == nil) { 86 return false 87 } 88 if len(b) != len(a) { 89 return false 90 } 91 for k, jsonA := range a { 92 jsonB, ok := b[k] 93 if !ok { 94 return false 95 } 96 if !bytes.Equal(jsonA, jsonB) { 97 return false 98 } 99 } 100 return true 101 } 102 103 // This struct resembles the JSON representation of the loadBalancing config 104 // and makes it easier to unmarshal. 105 type lbConfigJSON struct { 106 RouteLookupConfig json.RawMessage 107 RouteLookupChannelServiceConfig json.RawMessage 108 ChildPolicy []map[string]json.RawMessage 109 ChildPolicyConfigTargetFieldName string 110 } 111 112 // ParseConfig parses the JSON load balancer config provided into an 113 // internal form or returns an error if the config is invalid. 114 // 115 // When parsing a config update, the following validations are performed: 116 // - routeLookupConfig: 117 // - grpc_keybuilders field: 118 // - must have at least one entry 119 // - must not have two entries with the same `Name` 120 // - within each entry: 121 // - must have at least one `Name` 122 // - must not have a `Name` with the `service` field unset or empty 123 // - within each `headers` entry: 124 // - must not have `required_match` set 125 // - must not have `key` unset or empty 126 // - across all `headers`, `constant_keys` and `extra_keys` fields: 127 // - must not have the same `key` specified twice 128 // - no `key` must be the empty string 129 // - `lookup_service` field must be set and must parse as a target URI 130 // - if `max_age` > 5m, it should be set to 5 minutes 131 // - if `stale_age` > `max_age`, ignore it 132 // - if `stale_age` is set, then `max_age` must also be set 133 // - ignore `valid_targets` field 134 // - `cache_size_bytes` field must have a value greater than 0, and if its 135 // value is greater than 5M, we cap it at 5M 136 // 137 // - routeLookupChannelServiceConfig: 138 // - if specified, must parse as valid service config 139 // 140 // - childPolicy: 141 // - must find a valid child policy with a valid config 142 // 143 // - childPolicyConfigTargetFieldName: 144 // - must be set and non-empty 145 func (rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 146 if logger.V(2) { 147 logger.Infof("Received JSON service config: %v", pretty.ToJSON(c)) 148 } 149 150 cfgJSON := &lbConfigJSON{} 151 if err := json.Unmarshal(c, cfgJSON); err != nil { 152 return nil, fmt.Errorf("rls: json unmarshal failed for service config %+v: %v", string(c), err) 153 } 154 155 m := protojson.UnmarshalOptions{DiscardUnknown: true} 156 rlsProto := &rlspb.RouteLookupConfig{} 157 if err := m.Unmarshal(cfgJSON.RouteLookupConfig, rlsProto); err != nil { 158 return nil, fmt.Errorf("rls: bad RouteLookupConfig proto %+v: %v", string(cfgJSON.RouteLookupConfig), err) 159 } 160 lbCfg, err := parseRLSProto(rlsProto) 161 if err != nil { 162 return nil, err 163 } 164 165 if sc := string(cfgJSON.RouteLookupChannelServiceConfig); sc != "" { 166 parsed := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(sc) 167 if parsed.Err != nil { 168 return nil, fmt.Errorf("rls: bad control channel service config %q: %v", sc, parsed.Err) 169 } 170 lbCfg.controlChannelServiceConfig = sc 171 } 172 173 if cfgJSON.ChildPolicyConfigTargetFieldName == "" { 174 return nil, fmt.Errorf("rls: childPolicyConfigTargetFieldName field is not set in service config %+v", string(c)) 175 } 176 name, config, err := parseChildPolicyConfigs(cfgJSON.ChildPolicy, cfgJSON.ChildPolicyConfigTargetFieldName) 177 if err != nil { 178 return nil, err 179 } 180 lbCfg.childPolicyName = name 181 lbCfg.childPolicyConfig = config 182 lbCfg.childPolicyTargetField = cfgJSON.ChildPolicyConfigTargetFieldName 183 return lbCfg, nil 184 } 185 186 func parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) { 187 // Validations specified on the `grpc_keybuilders` field are performed here. 188 kbMap, err := keys.MakeBuilderMap(rlsProto) 189 if err != nil { 190 return nil, err 191 } 192 193 // `lookup_service` field must be set and must parse as a target URI. 194 lookupService := rlsProto.GetLookupService() 195 if lookupService == "" { 196 return nil, fmt.Errorf("rls: empty lookup_service in route lookup config %+v", rlsProto) 197 } 198 parsedTarget, err := url.Parse(lookupService) 199 if err != nil { 200 // url.Parse() fails if scheme is missing. Retry with default scheme. 201 parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService) 202 if err != nil { 203 return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService) 204 } 205 } 206 if parsedTarget.Scheme == "" { 207 parsedTarget.Scheme = resolver.GetDefaultScheme() 208 } 209 if resolver.Get(parsedTarget.Scheme) == nil { 210 return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService) 211 } 212 213 lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout()) 214 if err != nil { 215 return nil, fmt.Errorf("rls: failed to parse lookup_service_timeout in route lookup config %+v: %v", rlsProto, err) 216 } 217 if lookupServiceTimeout == 0 { 218 lookupServiceTimeout = defaultLookupServiceTimeout 219 } 220 221 // Validations performed here: 222 // - if `max_age` > 5m, it should be set to 5 minutes 223 // only if stale age is not set 224 // - if `stale_age` > `max_age`, ignore it 225 // - if `stale_age` is set, then `max_age` must also be set 226 maxAgeSet := false 227 maxAge, err := convertDuration(rlsProto.GetMaxAge()) 228 if err != nil { 229 return nil, fmt.Errorf("rls: failed to parse max_age in route lookup config %+v: %v", rlsProto, err) 230 } 231 if maxAge == 0 { 232 maxAge = maxMaxAge 233 } else { 234 maxAgeSet = true 235 } 236 237 staleAgeSet := false 238 staleAge, err := convertDuration(rlsProto.GetStaleAge()) 239 if err != nil { 240 return nil, fmt.Errorf("rls: failed to parse staleAge in route lookup config %+v: %v", rlsProto, err) 241 } 242 if staleAge == 0 { 243 staleAge = maxMaxAge 244 } else { 245 staleAgeSet = true 246 } 247 248 if staleAgeSet && !maxAgeSet { 249 return nil, fmt.Errorf("rls: stale_age is set, but max_age is not in route lookup config %+v", rlsProto) 250 } 251 if staleAge > maxMaxAge { 252 staleAge = maxMaxAge 253 } 254 if !staleAgeSet && maxAge > maxMaxAge { 255 maxAge = maxMaxAge 256 } 257 if staleAge > maxAge { 258 staleAge = maxAge 259 } 260 261 // `cache_size_bytes` field must have a value greater than 0, and if its 262 // value is greater than 5M, we cap it at 5M 263 cacheSizeBytes := rlsProto.GetCacheSizeBytes() 264 if cacheSizeBytes <= 0 { 265 return nil, fmt.Errorf("rls: cache_size_bytes must be set to a non-zero value: %+v", rlsProto) 266 } 267 if cacheSizeBytes > maxCacheSize { 268 logger.Info("rls: cache_size_bytes %v is too large, setting it to: %v", cacheSizeBytes, maxCacheSize) 269 cacheSizeBytes = maxCacheSize 270 } 271 return &lbConfig{ 272 kbMap: kbMap, 273 lookupService: lookupService, 274 lookupServiceTimeout: lookupServiceTimeout, 275 maxAge: maxAge, 276 staleAge: staleAge, 277 cacheSizeBytes: cacheSizeBytes, 278 defaultTarget: rlsProto.GetDefaultTarget(), 279 }, nil 280 } 281 282 // parseChildPolicyConfigs iterates through the list of child policies and picks 283 // the first registered policy and validates its config. 284 func parseChildPolicyConfigs(childPolicies []map[string]json.RawMessage, targetFieldName string) (string, map[string]json.RawMessage, error) { 285 for i, config := range childPolicies { 286 if len(config) != 1 { 287 return "", nil, fmt.Errorf("rls: invalid childPolicy: entry %v does not contain exactly 1 policy/config pair: %q", i, config) 288 } 289 290 var name string 291 var rawCfg json.RawMessage 292 for name, rawCfg = range config { 293 } 294 builder := balancer.Get(name) 295 if builder == nil { 296 continue 297 } 298 parser, ok := builder.(balancer.ConfigParser) 299 if !ok { 300 return "", nil, fmt.Errorf("rls: childPolicy %q with config %q does not support config parsing", name, string(rawCfg)) 301 } 302 303 // To validate child policy configs we do the following: 304 // - unmarshal the raw JSON bytes of the child policy config into a map 305 // - add an entry with key set to `target_field_name` and a dummy value 306 // - marshal the map back to JSON and parse the config using the parser 307 // retrieved previously 308 var childConfig map[string]json.RawMessage 309 if err := json.Unmarshal(rawCfg, &childConfig); err != nil { 310 return "", nil, fmt.Errorf("rls: json unmarshal failed for child policy config %q: %v", string(rawCfg), err) 311 } 312 childConfig[targetFieldName], _ = json.Marshal(dummyChildPolicyTarget) 313 jsonCfg, err := json.Marshal(childConfig) 314 if err != nil { 315 return "", nil, fmt.Errorf("rls: json marshal failed for child policy config {%+v}: %v", childConfig, err) 316 } 317 if _, err := parser.ParseConfig(jsonCfg); err != nil { 318 return "", nil, fmt.Errorf("rls: childPolicy config validation failed: %v", err) 319 } 320 return name, childConfig, nil 321 } 322 return "", nil, fmt.Errorf("rls: invalid childPolicy config: no supported policies found in %+v", childPolicies) 323 } 324 325 func convertDuration(d *durationpb.Duration) (time.Duration, error) { 326 if d == nil { 327 return 0, nil 328 } 329 return d.AsDuration(), d.CheckValid() 330 }