google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/xdsresource/unmarshal_cds.go (about) 1 /* 2 * 3 * Copyright 2021 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 package xdsresource 19 20 import ( 21 "encoding/json" 22 "errors" 23 "fmt" 24 "net" 25 "strconv" 26 "strings" 27 "time" 28 29 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 30 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 31 v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3" 32 v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 33 34 "google.golang.org/grpc/internal/envconfig" 35 "google.golang.org/grpc/internal/pretty" 36 iserviceconfig "google.golang.org/grpc/internal/serviceconfig" 37 "google.golang.org/grpc/internal/xds/matcher" 38 "google.golang.org/grpc/xds/internal/xdsclient/xdslbregistry" 39 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" 40 "google.golang.org/protobuf/proto" 41 "google.golang.org/protobuf/types/known/anypb" 42 ) 43 44 // ValidateClusterAndConstructClusterUpdateForTesting exports the 45 // validateClusterAndConstructClusterUpdate function for testing purposes. 46 var ValidateClusterAndConstructClusterUpdateForTesting = validateClusterAndConstructClusterUpdate 47 48 // TransportSocket proto message has a `name` field which is expected to be set 49 // to this value by the management server. 50 const transportSocketName = "envoy.transport_sockets.tls" 51 52 func unmarshalClusterResource(r *anypb.Any) (string, ClusterUpdate, error) { 53 r, err := UnwrapResource(r) 54 if err != nil { 55 return "", ClusterUpdate{}, fmt.Errorf("failed to unwrap resource: %v", err) 56 } 57 58 if !IsClusterResource(r.GetTypeUrl()) { 59 return "", ClusterUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) 60 } 61 62 cluster := &v3clusterpb.Cluster{} 63 if err := proto.Unmarshal(r.GetValue(), cluster); err != nil { 64 return "", ClusterUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) 65 } 66 cu, err := validateClusterAndConstructClusterUpdate(cluster) 67 if err != nil { 68 return cluster.GetName(), ClusterUpdate{}, err 69 } 70 cu.Raw = r 71 72 return cluster.GetName(), cu, nil 73 } 74 75 const ( 76 defaultRingHashMinSize = 1024 77 defaultRingHashMaxSize = 8 * 1024 * 1024 // 8M 78 ringHashSizeUpperBound = 8 * 1024 * 1024 // 8M 79 80 defaultLeastRequestChoiceCount = 2 81 ) 82 83 func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster) (ClusterUpdate, error) { 84 var lbPolicy json.RawMessage 85 var err error 86 switch cluster.GetLbPolicy() { 87 case v3clusterpb.Cluster_ROUND_ROBIN: 88 lbPolicy = []byte(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`) 89 case v3clusterpb.Cluster_RING_HASH: 90 rhc := cluster.GetRingHashLbConfig() 91 if rhc.GetHashFunction() != v3clusterpb.Cluster_RingHashLbConfig_XX_HASH { 92 return ClusterUpdate{}, fmt.Errorf("unsupported ring_hash hash function %v in response: %+v", rhc.GetHashFunction(), cluster) 93 } 94 // Minimum defaults to 1024 entries, and limited to 8M entries Maximum 95 // defaults to 8M entries, and limited to 8M entries 96 var minSize, maxSize uint64 = defaultRingHashMinSize, defaultRingHashMaxSize 97 if min := rhc.GetMinimumRingSize(); min != nil { 98 minSize = min.GetValue() 99 } 100 if max := rhc.GetMaximumRingSize(); max != nil { 101 maxSize = max.GetValue() 102 } 103 104 rhLBCfg := []byte(fmt.Sprintf("{\"minRingSize\": %d, \"maxRingSize\": %d}", minSize, maxSize)) 105 lbPolicy = []byte(fmt.Sprintf(`[{"ring_hash_experimental": %s}]`, rhLBCfg)) 106 case v3clusterpb.Cluster_LEAST_REQUEST: 107 if !envconfig.LeastRequestLB { 108 return ClusterUpdate{}, fmt.Errorf("unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster) 109 } 110 111 // "The configuration for the Least Request LB policy is the 112 // least_request_lb_config field. The field is optional; if not present, 113 // defaults will be assumed for all of its values." - A48 114 lr := cluster.GetLeastRequestLbConfig() 115 var choiceCount uint32 = defaultLeastRequestChoiceCount 116 if cc := lr.GetChoiceCount(); cc != nil { 117 choiceCount = cc.GetValue() 118 } 119 // "If choice_count < 2, the config will be rejected." - A48 120 if choiceCount < 2 { 121 return ClusterUpdate{}, fmt.Errorf("Cluster_LeastRequestLbConfig.ChoiceCount must be >= 2, got: %v", choiceCount) 122 } 123 124 lrLBCfg := []byte(fmt.Sprintf("{\"choiceCount\": %d}", choiceCount)) 125 lbPolicy = []byte(fmt.Sprintf(`[{"least_request_experimental": %s}]`, lrLBCfg)) 126 default: 127 return ClusterUpdate{}, fmt.Errorf("unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster) 128 } 129 // Process security configuration received from the control plane iff the 130 // corresponding environment variable is set. 131 var sc *SecurityConfig 132 if sc, err = securityConfigFromCluster(cluster); err != nil { 133 return ClusterUpdate{}, err 134 } 135 136 // Process outlier detection received from the control plane iff the 137 // corresponding environment variable is set. 138 var od json.RawMessage 139 if od, err = outlierConfigFromCluster(cluster); err != nil { 140 return ClusterUpdate{}, err 141 } 142 143 if cluster.GetLoadBalancingPolicy() != nil { 144 lbPolicy, err = xdslbregistry.ConvertToServiceConfig(cluster.GetLoadBalancingPolicy(), 0) 145 if err != nil { 146 return ClusterUpdate{}, fmt.Errorf("error converting LoadBalancingPolicy %v in response: %+v: %v", cluster.GetLoadBalancingPolicy(), cluster, err) 147 } 148 // "It will be the responsibility of the XdsClient to validate the 149 // converted configuration. It will do this by having the gRPC LB policy 150 // registry parse the configuration." - A52 151 bc := &iserviceconfig.BalancerConfig{} 152 if err := json.Unmarshal(lbPolicy, bc); err != nil { 153 return ClusterUpdate{}, fmt.Errorf("JSON generated from xDS LB policy registry: %s is invalid: %v", pretty.FormatJSON(lbPolicy), err) 154 } 155 } 156 157 ret := ClusterUpdate{ 158 ClusterName: cluster.GetName(), 159 SecurityCfg: sc, 160 MaxRequests: circuitBreakersFromCluster(cluster), 161 LBPolicy: lbPolicy, 162 OutlierDetection: od, 163 } 164 165 // Note that this is different from the gRFC (gRFC A47 says to include the 166 // full ServerConfig{URL,creds,server feature} here). This information is 167 // not available here, because this function doesn't have access to the 168 // xdsclient bootstrap information now (can be added if necessary). The 169 // ServerConfig will be read and populated by the CDS balancer when 170 // processing this field. 171 // According to A27: 172 // If the `lrs_server` field is set, it must have its `self` field set, in 173 // which case the client should use LRS for load reporting. Otherwise 174 // (the `lrs_server` field is not set), LRS load reporting will be disabled. 175 if lrs := cluster.GetLrsServer(); lrs != nil { 176 if lrs.GetSelf() == nil { 177 return ClusterUpdate{}, fmt.Errorf("unsupported config_source_specifier %T in lrs_server field", lrs.ConfigSourceSpecifier) 178 } 179 ret.LRSServerConfig = ClusterLRSServerSelf 180 } 181 182 // Validate and set cluster type from the response. 183 switch { 184 case cluster.GetType() == v3clusterpb.Cluster_EDS: 185 if configsource := cluster.GetEdsClusterConfig().GetEdsConfig(); configsource.GetAds() == nil && configsource.GetSelf() == nil { 186 return ClusterUpdate{}, fmt.Errorf("CDS's EDS config source is not ADS or Self: %+v", cluster) 187 } 188 ret.ClusterType = ClusterTypeEDS 189 ret.EDSServiceName = cluster.GetEdsClusterConfig().GetServiceName() 190 if strings.HasPrefix(ret.ClusterName, "xdstp:") && ret.EDSServiceName == "" { 191 return ClusterUpdate{}, fmt.Errorf("CDS's EDS service name is not set with a new-style cluster name: %+v", cluster) 192 } 193 return ret, nil 194 case cluster.GetType() == v3clusterpb.Cluster_LOGICAL_DNS: 195 ret.ClusterType = ClusterTypeLogicalDNS 196 dnsHN, err := dnsHostNameFromCluster(cluster) 197 if err != nil { 198 return ClusterUpdate{}, err 199 } 200 ret.DNSHostName = dnsHN 201 return ret, nil 202 case cluster.GetClusterType() != nil && cluster.GetClusterType().Name == "envoy.clusters.aggregate": 203 clusters := &v3aggregateclusterpb.ClusterConfig{} 204 if err := proto.Unmarshal(cluster.GetClusterType().GetTypedConfig().GetValue(), clusters); err != nil { 205 return ClusterUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) 206 } 207 if len(clusters.Clusters) == 0 { 208 return ClusterUpdate{}, fmt.Errorf("xds: aggregate cluster has empty clusters field in response: %+v", cluster) 209 } 210 ret.ClusterType = ClusterTypeAggregate 211 ret.PrioritizedClusterNames = clusters.Clusters 212 return ret, nil 213 default: 214 return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) 215 } 216 } 217 218 // dnsHostNameFromCluster extracts the DNS host name from the cluster's load 219 // assignment. 220 // 221 // There should be exactly one locality, with one endpoint, whose address 222 // contains the address and port. 223 func dnsHostNameFromCluster(cluster *v3clusterpb.Cluster) (string, error) { 224 loadAssignment := cluster.GetLoadAssignment() 225 if loadAssignment == nil { 226 return "", fmt.Errorf("load_assignment not present for LOGICAL_DNS cluster") 227 } 228 if len(loadAssignment.GetEndpoints()) != 1 { 229 return "", fmt.Errorf("load_assignment for LOGICAL_DNS cluster must have exactly one locality, got: %+v", loadAssignment) 230 } 231 endpoints := loadAssignment.GetEndpoints()[0].GetLbEndpoints() 232 if len(endpoints) != 1 { 233 return "", fmt.Errorf("locality for LOGICAL_DNS cluster must have exactly one endpoint, got: %+v", endpoints) 234 } 235 endpoint := endpoints[0].GetEndpoint() 236 if endpoint == nil { 237 return "", fmt.Errorf("endpoint for LOGICAL_DNS cluster not set") 238 } 239 socketAddr := endpoint.GetAddress().GetSocketAddress() 240 if socketAddr == nil { 241 return "", fmt.Errorf("socket address for endpoint for LOGICAL_DNS cluster not set") 242 } 243 if socketAddr.GetResolverName() != "" { 244 return "", fmt.Errorf("socket address for endpoint for LOGICAL_DNS cluster not set has unexpected custom resolver name: %v", socketAddr.GetResolverName()) 245 } 246 host := socketAddr.GetAddress() 247 if host == "" { 248 return "", fmt.Errorf("host for endpoint for LOGICAL_DNS cluster not set") 249 } 250 port := socketAddr.GetPortValue() 251 if port == 0 { 252 return "", fmt.Errorf("port for endpoint for LOGICAL_DNS cluster not set") 253 } 254 return net.JoinHostPort(host, strconv.Itoa(int(port))), nil 255 } 256 257 // securityConfigFromCluster extracts the relevant security configuration from 258 // the received Cluster resource. 259 func securityConfigFromCluster(cluster *v3clusterpb.Cluster) (*SecurityConfig, error) { 260 if tsm := cluster.GetTransportSocketMatches(); len(tsm) != 0 { 261 return nil, fmt.Errorf("unsupport transport_socket_matches field is non-empty: %+v", tsm) 262 } 263 // The Cluster resource contains a `transport_socket` field, which contains 264 // a oneof `typed_config` field of type `protobuf.Any`. The any proto 265 // contains a marshaled representation of an `UpstreamTlsContext` message. 266 ts := cluster.GetTransportSocket() 267 if ts == nil { 268 return nil, nil 269 } 270 if name := ts.GetName(); name != transportSocketName { 271 return nil, fmt.Errorf("transport_socket field has unexpected name: %s", name) 272 } 273 any := ts.GetTypedConfig() 274 if any == nil || any.TypeUrl != version.V3UpstreamTLSContextURL { 275 return nil, fmt.Errorf("transport_socket field has unexpected typeURL: %s", any.TypeUrl) 276 } 277 upstreamCtx := &v3tlspb.UpstreamTlsContext{} 278 if err := proto.Unmarshal(any.GetValue(), upstreamCtx); err != nil { 279 return nil, fmt.Errorf("failed to unmarshal UpstreamTlsContext in CDS response: %v", err) 280 } 281 // The following fields from `UpstreamTlsContext` are ignored: 282 // - sni 283 // - allow_renegotiation 284 // - max_session_keys 285 if upstreamCtx.GetCommonTlsContext() == nil { 286 return nil, errors.New("UpstreamTlsContext in CDS response does not contain a CommonTlsContext") 287 } 288 289 return securityConfigFromCommonTLSContext(upstreamCtx.GetCommonTlsContext(), false) 290 } 291 292 // common is expected to be not nil. 293 // The `alpn_protocols` field is ignored. 294 func securityConfigFromCommonTLSContext(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { 295 if common.GetTlsParams() != nil { 296 return nil, fmt.Errorf("unsupported tls_params field in CommonTlsContext message: %+v", common) 297 } 298 if common.GetCustomHandshaker() != nil { 299 return nil, fmt.Errorf("unsupported custom_handshaker field in CommonTlsContext message: %+v", common) 300 } 301 302 // For now, if we can't get a valid security config from the new fields, we 303 // fallback to the old deprecated fields. 304 // TODO: Drop support for deprecated fields. NACK if err != nil here. 305 sc, _ := securityConfigFromCommonTLSContextUsingNewFields(common, server) 306 if sc == nil || sc.Equal(&SecurityConfig{}) { 307 var err error 308 sc, err = securityConfigFromCommonTLSContextWithDeprecatedFields(common, server) 309 if err != nil { 310 return nil, err 311 } 312 } 313 if sc != nil { 314 // sc == nil is a valid case where the control plane has not sent us any 315 // security configuration. xDS creds will use fallback creds. 316 if server { 317 if sc.IdentityInstanceName == "" { 318 return nil, errors.New("security configuration on the server-side does not contain identity certificate provider instance name") 319 } 320 } else { 321 if sc.RootInstanceName == "" { 322 return nil, errors.New("security configuration on the client-side does not contain root certificate provider instance name") 323 } 324 } 325 } 326 return sc, nil 327 } 328 329 func securityConfigFromCommonTLSContextWithDeprecatedFields(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { 330 // The `CommonTlsContext` contains a 331 // `tls_certificate_certificate_provider_instance` field of type 332 // `CertificateProviderInstance`, which contains the provider instance name 333 // and the certificate name to fetch identity certs. 334 sc := &SecurityConfig{} 335 if identity := common.GetTlsCertificateCertificateProviderInstance(); identity != nil { 336 sc.IdentityInstanceName = identity.GetInstanceName() 337 sc.IdentityCertName = identity.GetCertificateName() 338 } 339 340 // The `CommonTlsContext` contains a `validation_context_type` field which 341 // is a oneof. We can get the values that we are interested in from two of 342 // those possible values: 343 // - combined validation context: 344 // - contains a default validation context which holds the list of 345 // matchers for accepted SANs. 346 // - contains certificate provider instance configuration 347 // - certificate provider instance configuration 348 // - in this case, we do not get a list of accepted SANs. 349 switch t := common.GetValidationContextType().(type) { 350 case *v3tlspb.CommonTlsContext_CombinedValidationContext: 351 combined := common.GetCombinedValidationContext() 352 var matchers []matcher.StringMatcher 353 if def := combined.GetDefaultValidationContext(); def != nil { 354 for _, m := range def.GetMatchSubjectAltNames() { 355 matcher, err := matcher.StringMatcherFromProto(m) 356 if err != nil { 357 return nil, err 358 } 359 matchers = append(matchers, matcher) 360 } 361 } 362 if server && len(matchers) != 0 { 363 return nil, fmt.Errorf("match_subject_alt_names field in validation context is not supported on the server: %v", common) 364 } 365 sc.SubjectAltNameMatchers = matchers 366 if pi := combined.GetValidationContextCertificateProviderInstance(); pi != nil { 367 sc.RootInstanceName = pi.GetInstanceName() 368 sc.RootCertName = pi.GetCertificateName() 369 } 370 case *v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance: 371 pi := common.GetValidationContextCertificateProviderInstance() 372 sc.RootInstanceName = pi.GetInstanceName() 373 sc.RootCertName = pi.GetCertificateName() 374 case nil: 375 // It is valid for the validation context to be nil on the server side. 376 default: 377 return nil, fmt.Errorf("validation context contains unexpected type: %T", t) 378 } 379 return sc, nil 380 } 381 382 // gRFC A29 https://github.com/grpc/proposal/blob/master/A29-xds-tls-security.md 383 // specifies the new way to fetch security configuration and says the following: 384 // 385 // Although there are various ways to obtain certificates as per this proto 386 // (which are supported by Envoy), gRPC supports only one of them and that is 387 // the `CertificateProviderPluginInstance` proto. 388 // 389 // This helper function attempts to fetch security configuration from the 390 // `CertificateProviderPluginInstance` message, given a CommonTlsContext. 391 func securityConfigFromCommonTLSContextUsingNewFields(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { 392 // The `tls_certificate_provider_instance` field of type 393 // `CertificateProviderPluginInstance` is used to fetch the identity 394 // certificate provider. 395 sc := &SecurityConfig{} 396 identity := common.GetTlsCertificateProviderInstance() 397 if identity == nil && len(common.GetTlsCertificates()) != 0 { 398 return nil, fmt.Errorf("expected field tls_certificate_provider_instance is not set, while unsupported field tls_certificates is set in CommonTlsContext message: %+v", common) 399 } 400 if identity == nil && common.GetTlsCertificateSdsSecretConfigs() != nil { 401 return nil, fmt.Errorf("expected field tls_certificate_provider_instance is not set, while unsupported field tls_certificate_sds_secret_configs is set in CommonTlsContext message: %+v", common) 402 } 403 sc.IdentityInstanceName = identity.GetInstanceName() 404 sc.IdentityCertName = identity.GetCertificateName() 405 406 // The `CommonTlsContext` contains a oneof field `validation_context_type`, 407 // which contains the `CertificateValidationContext` message in one of the 408 // following ways: 409 // - `validation_context` field 410 // - this is directly of type `CertificateValidationContext` 411 // - `combined_validation_context` field 412 // - this is of type `CombinedCertificateValidationContext` and contains 413 // a `default validation context` field of type 414 // `CertificateValidationContext` 415 // 416 // The `CertificateValidationContext` message has the following fields that 417 // we are interested in: 418 // - `ca_certificate_provider_instance` 419 // - this is of type `CertificateProviderPluginInstance` 420 // - `match_subject_alt_names` 421 // - this is a list of string matchers 422 // 423 // The `CertificateProviderPluginInstance` message contains two fields 424 // - instance_name 425 // - this is the certificate provider instance name to be looked up in 426 // the bootstrap configuration 427 // - certificate_name 428 // - this is an opaque name passed to the certificate provider 429 var validationCtx *v3tlspb.CertificateValidationContext 430 switch typ := common.GetValidationContextType().(type) { 431 case *v3tlspb.CommonTlsContext_ValidationContext: 432 validationCtx = common.GetValidationContext() 433 case *v3tlspb.CommonTlsContext_CombinedValidationContext: 434 validationCtx = common.GetCombinedValidationContext().GetDefaultValidationContext() 435 case nil: 436 // It is valid for the validation context to be nil on the server side. 437 return sc, nil 438 default: 439 return nil, fmt.Errorf("validation context contains unexpected type: %T", typ) 440 } 441 // If we get here, it means that the `CertificateValidationContext` message 442 // was found through one of the supported ways. It is an error if the 443 // validation context is specified, but it does not contain the 444 // ca_certificate_provider_instance field which contains information about 445 // the certificate provider to be used for the root certificates. 446 if validationCtx.GetCaCertificateProviderInstance() == nil { 447 return nil, fmt.Errorf("expected field ca_certificate_provider_instance is missing in CommonTlsContext message: %+v", common) 448 } 449 // The following fields are ignored: 450 // - trusted_ca 451 // - watched_directory 452 // - allow_expired_certificate 453 // - trust_chain_verification 454 switch { 455 case len(validationCtx.GetVerifyCertificateSpki()) != 0: 456 return nil, fmt.Errorf("unsupported verify_certificate_spki field in CommonTlsContext message: %+v", common) 457 case len(validationCtx.GetVerifyCertificateHash()) != 0: 458 return nil, fmt.Errorf("unsupported verify_certificate_hash field in CommonTlsContext message: %+v", common) 459 case validationCtx.GetRequireSignedCertificateTimestamp().GetValue(): 460 return nil, fmt.Errorf("unsupported require_sugned_ceritificate_timestamp field in CommonTlsContext message: %+v", common) 461 case validationCtx.GetCrl() != nil: 462 return nil, fmt.Errorf("unsupported crl field in CommonTlsContext message: %+v", common) 463 case validationCtx.GetCustomValidatorConfig() != nil: 464 return nil, fmt.Errorf("unsupported custom_validator_config field in CommonTlsContext message: %+v", common) 465 } 466 467 if rootProvider := validationCtx.GetCaCertificateProviderInstance(); rootProvider != nil { 468 sc.RootInstanceName = rootProvider.GetInstanceName() 469 sc.RootCertName = rootProvider.GetCertificateName() 470 } 471 var matchers []matcher.StringMatcher 472 for _, m := range validationCtx.GetMatchSubjectAltNames() { 473 matcher, err := matcher.StringMatcherFromProto(m) 474 if err != nil { 475 return nil, err 476 } 477 matchers = append(matchers, matcher) 478 } 479 if server && len(matchers) != 0 { 480 return nil, fmt.Errorf("match_subject_alt_names field in validation context is not supported on the server: %v", common) 481 } 482 sc.SubjectAltNameMatchers = matchers 483 return sc, nil 484 } 485 486 // circuitBreakersFromCluster extracts the circuit breakers configuration from 487 // the received cluster resource. Returns nil if no CircuitBreakers or no 488 // Thresholds in CircuitBreakers. 489 func circuitBreakersFromCluster(cluster *v3clusterpb.Cluster) *uint32 { 490 for _, threshold := range cluster.GetCircuitBreakers().GetThresholds() { 491 if threshold.GetPriority() != v3corepb.RoutingPriority_DEFAULT { 492 continue 493 } 494 maxRequestsPb := threshold.GetMaxRequests() 495 if maxRequestsPb == nil { 496 return nil 497 } 498 maxRequests := maxRequestsPb.GetValue() 499 return &maxRequests 500 } 501 return nil 502 } 503 504 // idurationp takes a time.Duration and converts it to an internal duration, and 505 // returns a pointer to that internal duration. 506 func idurationp(d time.Duration) *iserviceconfig.Duration { 507 id := iserviceconfig.Duration(d) 508 return &id 509 } 510 511 func uint32p(i uint32) *uint32 { 512 return &i 513 } 514 515 // Helper types to prepare Outlier Detection JSON. Pointer types to distinguish 516 // between unset and a zero value. 517 type successRateEjection struct { 518 StdevFactor *uint32 `json:"stdevFactor,omitempty"` 519 EnforcementPercentage *uint32 `json:"enforcementPercentage,omitempty"` 520 MinimumHosts *uint32 `json:"minimumHosts,omitempty"` 521 RequestVolume *uint32 `json:"requestVolume,omitempty"` 522 } 523 524 type failurePercentageEjection struct { 525 Threshold *uint32 `json:"threshold,omitempty"` 526 EnforcementPercentage *uint32 `json:"enforcementPercentage,omitempty"` 527 MinimumHosts *uint32 `json:"minimumHosts,omitempty"` 528 RequestVolume *uint32 `json:"requestVolume,omitempty"` 529 } 530 531 type odLBConfig struct { 532 Interval *iserviceconfig.Duration `json:"interval,omitempty"` 533 BaseEjectionTime *iserviceconfig.Duration `json:"baseEjectionTime,omitempty"` 534 MaxEjectionTime *iserviceconfig.Duration `json:"maxEjectionTime,omitempty"` 535 MaxEjectionPercent *uint32 `json:"maxEjectionPercent,omitempty"` 536 SuccessRateEjection *successRateEjection `json:"successRateEjection,omitempty"` 537 FailurePercentageEjection *failurePercentageEjection `json:"failurePercentageEjection,omitempty"` 538 } 539 540 // outlierConfigFromCluster converts the received Outlier Detection 541 // configuration into JSON configuration for Outlier Detection, taking into 542 // account xDS Defaults. Returns nil if no OutlierDetection field set in the 543 // cluster resource. 544 func outlierConfigFromCluster(cluster *v3clusterpb.Cluster) (json.RawMessage, error) { 545 od := cluster.GetOutlierDetection() 546 if od == nil { 547 return nil, nil 548 } 549 550 // "The outlier_detection field of the Cluster resource should have its fields 551 // validated according to the rules for the corresponding LB policy config 552 // fields in the above "Validation" section. If any of these requirements is 553 // violated, the Cluster resource should be NACKed." - A50 554 // "The google.protobuf.Duration fields interval, base_ejection_time, and 555 // max_ejection_time must obey the restrictions in the 556 // google.protobuf.Duration documentation and they must have non-negative 557 // values." - A50 558 var interval *iserviceconfig.Duration 559 if i := od.GetInterval(); i != nil { 560 if err := i.CheckValid(); err != nil { 561 return nil, fmt.Errorf("outlier_detection.interval is invalid with error: %v", err) 562 } 563 if interval = idurationp(i.AsDuration()); *interval < 0 { 564 return nil, fmt.Errorf("outlier_detection.interval = %v; must be a valid duration and >= 0", *interval) 565 } 566 } 567 568 var baseEjectionTime *iserviceconfig.Duration 569 if bet := od.GetBaseEjectionTime(); bet != nil { 570 if err := bet.CheckValid(); err != nil { 571 return nil, fmt.Errorf("outlier_detection.base_ejection_time is invalid with error: %v", err) 572 } 573 if baseEjectionTime = idurationp(bet.AsDuration()); *baseEjectionTime < 0 { 574 return nil, fmt.Errorf("outlier_detection.base_ejection_time = %v; must be >= 0", *baseEjectionTime) 575 } 576 } 577 578 var maxEjectionTime *iserviceconfig.Duration 579 if met := od.GetMaxEjectionTime(); met != nil { 580 if err := met.CheckValid(); err != nil { 581 return nil, fmt.Errorf("outlier_detection.max_ejection_time is invalid: %v", err) 582 } 583 if maxEjectionTime = idurationp(met.AsDuration()); *maxEjectionTime < 0 { 584 return nil, fmt.Errorf("outlier_detection.max_ejection_time = %v; must be >= 0", *maxEjectionTime) 585 } 586 } 587 588 // "The fields max_ejection_percent, enforcing_success_rate, 589 // failure_percentage_threshold, and enforcing_failure_percentage must have 590 // values less than or equal to 100. If any of these requirements is 591 // violated, the Cluster resource should be NACKed." - A50 592 var maxEjectionPercent *uint32 593 if mep := od.GetMaxEjectionPercent(); mep != nil { 594 if maxEjectionPercent = uint32p(mep.GetValue()); *maxEjectionPercent > 100 { 595 return nil, fmt.Errorf("outlier_detection.max_ejection_percent = %v; must be <= 100", *maxEjectionPercent) 596 } 597 } 598 // "if the enforcing_success_rate field is set to 0, the config 599 // success_rate_ejection field will be null and all success_rate_* fields 600 // will be ignored." - A50 601 var enforcingSuccessRate *uint32 602 if esr := od.GetEnforcingSuccessRate(); esr != nil { 603 if enforcingSuccessRate = uint32p(esr.GetValue()); *enforcingSuccessRate > 100 { 604 return nil, fmt.Errorf("outlier_detection.enforcing_success_rate = %v; must be <= 100", *enforcingSuccessRate) 605 } 606 } 607 var failurePercentageThreshold *uint32 608 if fpt := od.GetFailurePercentageThreshold(); fpt != nil { 609 if failurePercentageThreshold = uint32p(fpt.GetValue()); *failurePercentageThreshold > 100 { 610 return nil, fmt.Errorf("outlier_detection.failure_percentage_threshold = %v; must be <= 100", *failurePercentageThreshold) 611 } 612 } 613 // "If the enforcing_failure_percent field is set to 0 or null, the config 614 // failure_percent_ejection field will be null and all failure_percent_* 615 // fields will be ignored." - A50 616 var enforcingFailurePercentage *uint32 617 if efp := od.GetEnforcingFailurePercentage(); efp != nil { 618 if enforcingFailurePercentage = uint32p(efp.GetValue()); *enforcingFailurePercentage > 100 { 619 return nil, fmt.Errorf("outlier_detection.enforcing_failure_percentage = %v; must be <= 100", *enforcingFailurePercentage) 620 } 621 } 622 623 var successRateStdevFactor *uint32 624 if srsf := od.GetSuccessRateStdevFactor(); srsf != nil { 625 successRateStdevFactor = uint32p(srsf.GetValue()) 626 } 627 var successRateMinimumHosts *uint32 628 if srmh := od.GetSuccessRateMinimumHosts(); srmh != nil { 629 successRateMinimumHosts = uint32p(srmh.GetValue()) 630 } 631 var successRateRequestVolume *uint32 632 if srrv := od.GetSuccessRateRequestVolume(); srrv != nil { 633 successRateRequestVolume = uint32p(srrv.GetValue()) 634 } 635 var failurePercentageMinimumHosts *uint32 636 if fpmh := od.GetFailurePercentageMinimumHosts(); fpmh != nil { 637 failurePercentageMinimumHosts = uint32p(fpmh.GetValue()) 638 } 639 var failurePercentageRequestVolume *uint32 640 if fprv := od.GetFailurePercentageRequestVolume(); fprv != nil { 641 failurePercentageRequestVolume = uint32p(fprv.GetValue()) 642 } 643 644 // "if the enforcing_success_rate field is set to 0, the config 645 // success_rate_ejection field will be null and all success_rate_* fields 646 // will be ignored." - A50 647 var sre *successRateEjection 648 if enforcingSuccessRate == nil || *enforcingSuccessRate != 0 { 649 sre = &successRateEjection{ 650 StdevFactor: successRateStdevFactor, 651 EnforcementPercentage: enforcingSuccessRate, 652 MinimumHosts: successRateMinimumHosts, 653 RequestVolume: successRateRequestVolume, 654 } 655 } 656 657 // "If the enforcing_failure_percent field is set to 0 or null, the config 658 // failure_percent_ejection field will be null and all failure_percent_* 659 // fields will be ignored." - A50 660 var fpe *failurePercentageEjection 661 if enforcingFailurePercentage != nil && *enforcingFailurePercentage != 0 { 662 fpe = &failurePercentageEjection{ 663 Threshold: failurePercentageThreshold, 664 EnforcementPercentage: enforcingFailurePercentage, 665 MinimumHosts: failurePercentageMinimumHosts, 666 RequestVolume: failurePercentageRequestVolume, 667 } 668 } 669 670 odLBCfg := &odLBConfig{ 671 Interval: interval, 672 BaseEjectionTime: baseEjectionTime, 673 MaxEjectionTime: maxEjectionTime, 674 MaxEjectionPercent: maxEjectionPercent, 675 SuccessRateEjection: sre, 676 FailurePercentageEjection: fpe, 677 } 678 return json.Marshal(odLBCfg) 679 }