go.uber.org/yarpc@v1.72.1/yarpcconfig/builder.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package yarpcconfig
    22  
    23  import (
    24  	"fmt"
    25  	"reflect"
    26  
    27  	"github.com/uber-go/mapdecode"
    28  	"go.uber.org/multierr"
    29  	"go.uber.org/yarpc"
    30  	"go.uber.org/yarpc/api/transport"
    31  	yarpctls "go.uber.org/yarpc/api/transport/tls"
    32  	"go.uber.org/yarpc/internal/config"
    33  )
    34  
    35  type buildableOutbounds struct {
    36  	Service string
    37  	Unary   *buildableOutbound
    38  	Oneway  *buildableOutbound
    39  	Stream  *buildableOutbound
    40  }
    41  
    42  type buildableInbound struct {
    43  	Transport string
    44  	Value     *buildable
    45  }
    46  
    47  type buildableOutbound struct {
    48  	TransportSpec *compiledTransportSpec
    49  	Value         *buildable
    50  }
    51  
    52  type builder struct {
    53  	Name string
    54  	kit  *Kit
    55  
    56  	// Transports that we actually need and their specs. We need a transport
    57  	// only if we have at least one inbound or outbound using it.
    58  	needTransports map[string]*compiledTransportSpec
    59  
    60  	transports map[string]*buildable
    61  	inbounds   []buildableInbound
    62  	clients    map[string]*buildableOutbounds
    63  }
    64  
    65  func newBuilder(name string, kit *Kit) *builder {
    66  	return &builder{
    67  		Name:           name,
    68  		kit:            kit,
    69  		needTransports: make(map[string]*compiledTransportSpec),
    70  		transports:     make(map[string]*buildable),
    71  		clients:        make(map[string]*buildableOutbounds),
    72  	}
    73  }
    74  
    75  func (b *builder) Build() (yarpc.Config, error) {
    76  	var (
    77  		transports = make(map[string]transport.Transport)
    78  		cfg        = yarpc.Config{Name: b.Name}
    79  		errs       error
    80  	)
    81  
    82  	for name, spec := range b.needTransports {
    83  		cv, ok := b.transports[name]
    84  
    85  		var err error
    86  		if !ok {
    87  			// No configuration provided for the transport. Use an empty map.
    88  			cv, err = spec.Transport.Decode(config.AttributeMap{}, config.InterpolateWith(b.kit.resolver))
    89  			if err != nil {
    90  				return yarpc.Config{}, err
    91  			}
    92  		}
    93  
    94  		transports[name], err = buildTransport(cv, b.kit)
    95  		if err != nil {
    96  			return yarpc.Config{}, err
    97  		}
    98  	}
    99  
   100  	for _, i := range b.inbounds {
   101  		ib, err := buildInbound(i.Value, transports[i.Transport], b.kit)
   102  		if err != nil {
   103  			errs = multierr.Append(errs, err)
   104  			continue
   105  		}
   106  		cfg.Inbounds = append(cfg.Inbounds, ib)
   107  	}
   108  
   109  	outbounds := make(yarpc.Outbounds, len(b.clients))
   110  	for ccname, c := range b.clients {
   111  		var err error
   112  
   113  		var ob transport.Outbounds
   114  		if c.Service != ccname {
   115  			ob.ServiceName = c.Service
   116  		}
   117  
   118  		kit := b.kit.withOutboundName(c.Service)
   119  		if o := c.Unary; o != nil {
   120  			ob.Unary, err = buildUnaryOutbound(o, transports[o.TransportSpec.Name], kit)
   121  			if err != nil {
   122  				errs = multierr.Append(errs, fmt.Errorf(`failed to configure unary outbound for %q: %v`, ccname, err))
   123  				continue
   124  			}
   125  		}
   126  		if o := c.Oneway; o != nil {
   127  			ob.Oneway, err = buildOnewayOutbound(o, transports[o.TransportSpec.Name], kit)
   128  			if err != nil {
   129  				errs = multierr.Append(errs, fmt.Errorf(`failed to configure oneway outbound for %q: %v`, ccname, err))
   130  				continue
   131  			}
   132  		}
   133  		if o := c.Stream; o != nil {
   134  			ob.Stream, err = buildStreamOutbound(o, transports[o.TransportSpec.Name], kit)
   135  			if err != nil {
   136  				errs = multierr.Append(errs, fmt.Errorf(`failed to configure stream outbound for %q: %v`, ccname, err))
   137  				continue
   138  			}
   139  		}
   140  
   141  		outbounds[ccname] = ob
   142  	}
   143  	if len(outbounds) > 0 {
   144  		cfg.Outbounds = outbounds
   145  	}
   146  
   147  	return cfg, errs
   148  }
   149  
   150  // buildTransport builds a Transport from the given value. This will panic if
   151  // the output type is not a Transport.
   152  func buildTransport(cv *buildable, k *Kit) (transport.Transport, error) {
   153  	result, err := cv.Build(k)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	return result.(transport.Transport), nil
   158  }
   159  
   160  // buildInbound builds an Inbound from the given value. This will panic if the
   161  // output type for this is not transport.Inbound.
   162  func buildInbound(cv *buildable, t transport.Transport, k *Kit) (transport.Inbound, error) {
   163  	result, err := cv.Build(t, k)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	return result.(transport.Inbound), nil
   168  }
   169  
   170  // buildUnaryOutbound builds an UnaryOutbound from the given value. This will panic
   171  // if the output type for this is not transport.UnaryOutbound.
   172  func buildUnaryOutbound(o *buildableOutbound, t transport.Transport, k *Kit) (transport.UnaryOutbound, error) {
   173  	result, err := o.Value.Build(t, k.withTransportSpec(o.TransportSpec))
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	return result.(transport.UnaryOutbound), nil
   178  }
   179  
   180  // buildOnewayOutbound builds an OnewayOutbound from the given value. This will
   181  // panic if the output type for this is not transport.OnewayOutbound.
   182  func buildOnewayOutbound(o *buildableOutbound, t transport.Transport, k *Kit) (transport.OnewayOutbound, error) {
   183  	result, err := o.Value.Build(t, k.withTransportSpec(o.TransportSpec))
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	return result.(transport.OnewayOutbound), nil
   188  }
   189  
   190  // buildStreamOutbound builds an StreamOutbound from the given value. This will
   191  // panic if the output type for this is not transport.StreamOutbound.
   192  func buildStreamOutbound(o *buildableOutbound, t transport.Transport, k *Kit) (transport.StreamOutbound, error) {
   193  	result, err := o.Value.Build(t, k.withTransportSpec(o.TransportSpec))
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	return result.(transport.StreamOutbound), nil
   198  }
   199  
   200  func (b *builder) AddTransportConfig(spec *compiledTransportSpec, attrs config.AttributeMap) error {
   201  	cv, err := spec.Transport.Decode(attrs, config.InterpolateWith(b.kit.resolver))
   202  	if err != nil {
   203  		return fmt.Errorf("failed to decode transport configuration: %v", err)
   204  	}
   205  
   206  	b.transports[spec.Name] = cv
   207  	return nil
   208  }
   209  
   210  func (b *builder) AddInboundConfig(spec *compiledTransportSpec, attrs config.AttributeMap) error {
   211  	if spec.Inbound == nil {
   212  		return fmt.Errorf("transport %q does not support inbound requests", spec.Name)
   213  	}
   214  
   215  	b.needTransport(spec)
   216  	cv, err := spec.Inbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook))
   217  	if err != nil {
   218  		return fmt.Errorf("failed to decode inbound configuration: %v", err)
   219  	}
   220  
   221  	b.inbounds = append(b.inbounds, buildableInbound{
   222  		Transport: spec.Name,
   223  		Value:     cv,
   224  	})
   225  	return nil
   226  }
   227  
   228  func (b *builder) AddImplicitOutbound(
   229  	spec *compiledTransportSpec, outboundKey, service string, attrs config.AttributeMap,
   230  ) error {
   231  	var errs error
   232  	supportsOutbound := false
   233  
   234  	if spec.SupportsUnaryOutbound() {
   235  		supportsOutbound = true
   236  		if err := b.AddUnaryOutbound(spec, outboundKey, service, attrs); err != nil {
   237  			errs = multierr.Append(errs, err)
   238  		}
   239  	}
   240  
   241  	if spec.SupportsOnewayOutbound() {
   242  		supportsOutbound = true
   243  		if err := b.AddOnewayOutbound(spec, outboundKey, service, attrs); err != nil {
   244  			errs = multierr.Append(errs, err)
   245  		}
   246  	}
   247  	if spec.SupportsStreamOutbound() {
   248  		supportsOutbound = true
   249  		if err := b.AddStreamOutbound(spec, outboundKey, service, attrs); err != nil {
   250  			errs = multierr.Append(errs, err)
   251  		}
   252  	}
   253  
   254  	if !supportsOutbound {
   255  		return fmt.Errorf("transport %q does not support outbound requests", spec.Name)
   256  	}
   257  
   258  	return errs
   259  }
   260  
   261  func (b *builder) AddUnaryOutbound(
   262  	spec *compiledTransportSpec, outboundKey, service string, attrs config.AttributeMap,
   263  ) error {
   264  	if spec.UnaryOutbound == nil {
   265  		return fmt.Errorf("transport %q does not support unary outbound requests", spec.Name)
   266  	}
   267  
   268  	b.needTransport(spec)
   269  	cv, err := spec.UnaryOutbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook))
   270  	if err != nil {
   271  		return fmt.Errorf("failed to decode unary outbound configuration: %v", err)
   272  	}
   273  
   274  	cc, ok := b.clients[outboundKey]
   275  	if !ok {
   276  		cc = &buildableOutbounds{Service: service}
   277  		b.clients[outboundKey] = cc
   278  	}
   279  
   280  	cc.Unary = &buildableOutbound{TransportSpec: spec, Value: cv}
   281  	return nil
   282  }
   283  
   284  func (b *builder) AddOnewayOutbound(
   285  	spec *compiledTransportSpec, outboundKey, service string, attrs config.AttributeMap,
   286  ) error {
   287  	if spec.OnewayOutbound == nil {
   288  		return fmt.Errorf("transport %q does not support oneway outbound requests", spec.Name)
   289  	}
   290  
   291  	b.needTransport(spec)
   292  	cv, err := spec.OnewayOutbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook))
   293  	if err != nil {
   294  		return fmt.Errorf("failed to decode oneway outbound configuration: %v", err)
   295  	}
   296  
   297  	cc, ok := b.clients[outboundKey]
   298  	if !ok {
   299  		cc = &buildableOutbounds{Service: service}
   300  		b.clients[outboundKey] = cc
   301  	}
   302  
   303  	cc.Oneway = &buildableOutbound{TransportSpec: spec, Value: cv}
   304  	return nil
   305  }
   306  
   307  func (b *builder) AddStreamOutbound(
   308  	spec *compiledTransportSpec, outboundKey, service string, attrs config.AttributeMap,
   309  ) error {
   310  	if spec.StreamOutbound == nil {
   311  		return fmt.Errorf("transport %q does not support stream outbound requests", spec.Name)
   312  	}
   313  
   314  	b.needTransport(spec)
   315  	cv, err := spec.StreamOutbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook))
   316  	if err != nil {
   317  		return fmt.Errorf("failed to decode stream outbound configuration: %v", err)
   318  	}
   319  
   320  	cc, ok := b.clients[outboundKey]
   321  	if !ok {
   322  		cc = &buildableOutbounds{Service: service}
   323  		b.clients[outboundKey] = cc
   324  	}
   325  
   326  	cc.Stream = &buildableOutbound{TransportSpec: spec, Value: cv}
   327  	return nil
   328  }
   329  
   330  func (b *builder) needTransport(spec *compiledTransportSpec) {
   331  	b.needTransports[spec.Name] = spec
   332  }
   333  
   334  func tlsModeDecodeHook(from, to reflect.Type, data reflect.Value) (reflect.Value, error) {
   335  	var mode yarpctls.Mode
   336  	if from.Kind() != reflect.String || to != reflect.TypeOf(mode) {
   337  		return data, nil
   338  	}
   339  
   340  	err := mode.UnmarshalText([]byte(data.String()))
   341  	return reflect.ValueOf(mode), err
   342  }