serviceconfig.go

  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 serviceconfig contains utility functions to parse service config.
 20package serviceconfig
 21
 22import (
 23	"encoding/json"
 24	"fmt"
 25	"time"
 26
 27	"google.golang.org/grpc/balancer"
 28	"google.golang.org/grpc/codes"
 29	"google.golang.org/grpc/grpclog"
 30	externalserviceconfig "google.golang.org/grpc/serviceconfig"
 31)
 32
 33var logger = grpclog.Component("core")
 34
 35// BalancerConfig wraps the name and config associated with one load balancing
 36// policy. It corresponds to a single entry of the loadBalancingConfig field
 37// from ServiceConfig.
 38//
 39// It implements the json.Unmarshaler interface.
 40//
 41// https://github.com/grpc/grpc-proto/blob/54713b1e8bc6ed2d4f25fb4dff527842150b91b2/grpc/service_config/service_config.proto#L247
 42type BalancerConfig struct {
 43	Name   string
 44	Config externalserviceconfig.LoadBalancingConfig
 45}
 46
 47type intermediateBalancerConfig []map[string]json.RawMessage
 48
 49// MarshalJSON implements the json.Marshaler interface.
 50//
 51// It marshals the balancer and config into a length-1 slice
 52// ([]map[string]config).
 53func (bc *BalancerConfig) MarshalJSON() ([]byte, error) {
 54	if bc.Config == nil {
 55		// If config is nil, return empty config `{}`.
 56		return []byte(fmt.Sprintf(`[{%q: %v}]`, bc.Name, "{}")), nil
 57	}
 58	c, err := json.Marshal(bc.Config)
 59	if err != nil {
 60		return nil, err
 61	}
 62	return []byte(fmt.Sprintf(`[{%q: %s}]`, bc.Name, c)), nil
 63}
 64
 65// UnmarshalJSON implements the json.Unmarshaler interface.
 66//
 67// ServiceConfig contains a list of loadBalancingConfigs, each with a name and
 68// config. This method iterates through that list in order, and stops at the
 69// first policy that is supported.
 70//   - If the config for the first supported policy is invalid, the whole service
 71//     config is invalid.
 72//   - If the list doesn't contain any supported policy, the whole service config
 73//     is invalid.
 74func (bc *BalancerConfig) UnmarshalJSON(b []byte) error {
 75	var ir intermediateBalancerConfig
 76	err := json.Unmarshal(b, &ir)
 77	if err != nil {
 78		return err
 79	}
 80
 81	var names []string
 82	for i, lbcfg := range ir {
 83		if len(lbcfg) != 1 {
 84			return fmt.Errorf("invalid loadBalancingConfig: entry %v does not contain exactly 1 policy/config pair: %q", i, lbcfg)
 85		}
 86
 87		var (
 88			name    string
 89			jsonCfg json.RawMessage
 90		)
 91		// Get the key:value pair from the map. We have already made sure that
 92		// the map contains a single entry.
 93		for name, jsonCfg = range lbcfg {
 94		}
 95
 96		names = append(names, name)
 97		builder := balancer.Get(name)
 98		if builder == nil {
 99			// If the balancer is not registered, move on to the next config.
100			// This is not an error.
101			continue
102		}
103		bc.Name = name
104
105		parser, ok := builder.(balancer.ConfigParser)
106		if !ok {
107			if string(jsonCfg) != "{}" {
108				logger.Warningf("non-empty balancer configuration %q, but balancer does not implement ParseConfig", string(jsonCfg))
109			}
110			// Stop at this, though the builder doesn't support parsing config.
111			return nil
112		}
113
114		cfg, err := parser.ParseConfig(jsonCfg)
115		if err != nil {
116			return fmt.Errorf("error parsing loadBalancingConfig for policy %q: %v", name, err)
117		}
118		bc.Config = cfg
119		return nil
120	}
121	// This is reached when the for loop iterates over all entries, but didn't
122	// return. This means we had a loadBalancingConfig slice but did not
123	// encounter a registered policy. The config is considered invalid in this
124	// case.
125	return fmt.Errorf("invalid loadBalancingConfig: no supported policies found in %v", names)
126}
127
128// MethodConfig defines the configuration recommended by the service providers for a
129// particular method.
130type MethodConfig struct {
131	// WaitForReady indicates whether RPCs sent to this method should wait until
132	// the connection is ready by default (!failfast). The value specified via the
133	// gRPC client API will override the value set here.
134	WaitForReady *bool
135	// Timeout is the default timeout for RPCs sent to this method. The actual
136	// deadline used will be the minimum of the value specified here and the value
137	// set by the application via the gRPC client API.  If either one is not set,
138	// then the other will be used.  If neither is set, then the RPC has no deadline.
139	Timeout *time.Duration
140	// MaxReqSize is the maximum allowed payload size for an individual request in a
141	// stream (client->server) in bytes. The size which is measured is the serialized
142	// payload after per-message compression (but before stream compression) in bytes.
143	// The actual value used is the minimum of the value specified here and the value set
144	// by the application via the gRPC client API. If either one is not set, then the other
145	// will be used.  If neither is set, then the built-in default is used.
146	MaxReqSize *int
147	// MaxRespSize is the maximum allowed payload size for an individual response in a
148	// stream (server->client) in bytes.
149	MaxRespSize *int
150	// RetryPolicy configures retry options for the method.
151	RetryPolicy *RetryPolicy
152}
153
154// RetryPolicy defines the go-native version of the retry policy defined by the
155// service config here:
156// https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config
157type RetryPolicy struct {
158	// MaxAttempts is the maximum number of attempts, including the original RPC.
159	//
160	// This field is required and must be two or greater.
161	MaxAttempts int
162
163	// Exponential backoff parameters. The initial retry attempt will occur at
164	// random(0, initialBackoff). In general, the nth attempt will occur at
165	// random(0,
166	//   min(initialBackoff*backoffMultiplier**(n-1), maxBackoff)).
167	//
168	// These fields are required and must be greater than zero.
169	InitialBackoff    time.Duration
170	MaxBackoff        time.Duration
171	BackoffMultiplier float64
172
173	// The set of status codes which may be retried.
174	//
175	// Status codes are specified as strings, e.g., "UNAVAILABLE".
176	//
177	// This field is required and must be non-empty.
178	// Note: a set is used to store this for easy lookup.
179	RetryableStatusCodes map[codes.Code]bool
180}