go.uber.org/yarpc@v1.72.1/yarpcconfig/configurator.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  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"os"
    29  
    30  	"go.uber.org/multierr"
    31  	"go.uber.org/yarpc"
    32  	"go.uber.org/yarpc/api/transport"
    33  	"go.uber.org/yarpc/internal/config"
    34  	"go.uber.org/yarpc/internal/interpolate"
    35  	"gopkg.in/yaml.v2"
    36  )
    37  
    38  // Configurator helps build Dispatchers using runtime configuration.
    39  //
    40  // A new Configurator does not know about any transports, peer lists, or peer
    41  // list updaters. Inform it about them by using the RegisterTransport,
    42  // RegisterPeerList, and RegisterPeerListUpdater functions, or their Must*
    43  // variants.
    44  type Configurator struct {
    45  	knownTransports       map[string]*compiledTransportSpec
    46  	knownPeerChoosers     map[string]*compiledPeerChooserSpec
    47  	knownPeerLists        map[string]*compiledPeerListSpec
    48  	knownPeerListUpdaters map[string]*compiledPeerListUpdaterSpec
    49  	knownCompressors      map[string]transport.Compressor
    50  	resolver              interpolate.VariableResolver
    51  }
    52  
    53  // New sets up a new empty Configurator. The returned Configurator does not
    54  // know about any Transports, peer lists, or peer list updaters.
    55  func New(opts ...Option) *Configurator {
    56  	c := &Configurator{
    57  		knownTransports:       make(map[string]*compiledTransportSpec),
    58  		knownPeerChoosers:     make(map[string]*compiledPeerChooserSpec),
    59  		knownPeerLists:        make(map[string]*compiledPeerListSpec),
    60  		knownPeerListUpdaters: make(map[string]*compiledPeerListUpdaterSpec),
    61  		knownCompressors:      make(map[string]transport.Compressor),
    62  		resolver:              os.LookupEnv,
    63  	}
    64  
    65  	for _, opt := range opts {
    66  		opt(c)
    67  	}
    68  
    69  	return c
    70  }
    71  
    72  // RegisterTransport registers a TransportSpec with the Configurator, teaching
    73  // it how to load configuration and build inbounds and outbounds for that
    74  // transport.
    75  //
    76  // An error is returned if the TransportSpec is invalid. Use
    77  // MustRegisterTransport if you want to panic in case of registration failure.
    78  //
    79  // If a transport with the same name already exists, it will be replaced.
    80  //
    81  // See TransportSpec for details on how to integrate your own transport with
    82  // the system.
    83  func (c *Configurator) RegisterTransport(t TransportSpec) error {
    84  	if t.Name == "" {
    85  		return errors.New("name is required")
    86  	}
    87  
    88  	spec, err := compileTransportSpec(&t)
    89  	if err != nil {
    90  		return fmt.Errorf("invalid TransportSpec for %q: %v", t.Name, err)
    91  	}
    92  
    93  	c.knownTransports[t.Name] = spec
    94  	return nil
    95  }
    96  
    97  // MustRegisterTransport registers the given TransportSpec with the
    98  // Configurator. This function panics if the TransportSpec is invalid.
    99  func (c *Configurator) MustRegisterTransport(t TransportSpec) {
   100  	if err := c.RegisterTransport(t); err != nil {
   101  		panic(err)
   102  	}
   103  }
   104  
   105  // RegisterPeerChooser registers a PeerChooserSpec with the given Configurator,
   106  // teaching it how to build peer choosers of this kind from configuration.
   107  //
   108  // An error is returned if the PeerChooserSpec is invalid. Use
   109  // MustRegisterPeerChooser to panic in the case of registration failure.
   110  //
   111  // If a peer chooser with the same name already exists, it will be replaced.
   112  //
   113  // If a peer list is registered with the same name, it will be ignored.
   114  //
   115  // See PeerChooserSpec for details on how to integrate your own peer chooser
   116  // with the system.
   117  func (c *Configurator) RegisterPeerChooser(s PeerChooserSpec) error {
   118  	if s.Name == "" {
   119  		return errors.New("name is required")
   120  	}
   121  
   122  	spec, err := compilePeerChooserSpec(&s)
   123  	if err != nil {
   124  		return fmt.Errorf("invalid PeerChooserSpec for %q: %v", s.Name, err)
   125  	}
   126  
   127  	c.knownPeerChoosers[s.Name] = spec
   128  	return nil
   129  }
   130  
   131  // MustRegisterPeerChooser registers the given PeerChooserSpec with the
   132  // Configurator.
   133  // This function panics if the PeerChooserSpec is invalid.
   134  func (c *Configurator) MustRegisterPeerChooser(s PeerChooserSpec) {
   135  	if err := c.RegisterPeerChooser(s); err != nil {
   136  		panic(err)
   137  	}
   138  }
   139  
   140  // RegisterPeerList registers a PeerListSpec with the given Configurator,
   141  // teaching it how to build peer lists of this kind from configuration.
   142  //
   143  // An error is returned if the PeerListSpec is invalid. Use
   144  // MustRegisterPeerList to panic in the case of registration failure.
   145  //
   146  // If a peer list with the same name already exists, it will be replaced.
   147  //
   148  // If a peer chooser is registered with the same name, this list will be
   149  // ignored.
   150  //
   151  // See PeerListSpec for details on how to integrate your own peer list with
   152  // the system.
   153  func (c *Configurator) RegisterPeerList(s PeerListSpec) error {
   154  	if s.Name == "" {
   155  		return errors.New("name is required")
   156  	}
   157  
   158  	spec, err := compilePeerListSpec(&s)
   159  	if err != nil {
   160  		return fmt.Errorf("invalid PeerListSpec for %q: %v", s.Name, err)
   161  	}
   162  
   163  	c.knownPeerLists[s.Name] = spec
   164  	return nil
   165  }
   166  
   167  // MustRegisterPeerList registers the given PeerListSpec with the Configurator.
   168  // This function panics if the PeerListSpec is invalid.
   169  func (c *Configurator) MustRegisterPeerList(s PeerListSpec) {
   170  	if err := c.RegisterPeerList(s); err != nil {
   171  		panic(err)
   172  	}
   173  }
   174  
   175  // RegisterPeerListUpdater registers a PeerListUpdaterSpec with the given
   176  // Configurator, teaching it how to build peer list updaters of this kind from
   177  // configuration.
   178  //
   179  // Returns an error if the PeerListUpdaterSpec is invalid.  Use
   180  // MustRegisterPeerListUpdater to panic if the registration fails.
   181  //
   182  // If a peer list updater with the same name already exists, it will be
   183  // replaced.
   184  //
   185  // See PeerListUpdaterSpec for details on how to integrate your own peer list
   186  // updater with the system.
   187  func (c *Configurator) RegisterPeerListUpdater(s PeerListUpdaterSpec) error {
   188  	if s.Name == "" {
   189  		return errors.New("name is required")
   190  	}
   191  
   192  	spec, err := compilePeerListUpdaterSpec(&s)
   193  	if err != nil {
   194  		return fmt.Errorf("invalid PeerListUpdaterSpec for %q: %v", s.Name, err)
   195  	}
   196  
   197  	c.knownPeerListUpdaters[s.Name] = spec
   198  	return nil
   199  }
   200  
   201  // MustRegisterPeerListUpdater registers the given PeerListUpdaterSpec with
   202  // the Configurator. This function panics if the PeerListUpdaterSpec is
   203  // invalid.
   204  func (c *Configurator) MustRegisterPeerListUpdater(s PeerListUpdaterSpec) {
   205  	if err := c.RegisterPeerListUpdater(s); err != nil {
   206  		panic(err)
   207  	}
   208  }
   209  
   210  // RegisterCompressor registers the given Compressor for the configurator, so
   211  // any transport can use the given compression strategy.
   212  func (c *Configurator) RegisterCompressor(z transport.Compressor) error {
   213  	if c.knownCompressors[z.Name()] != nil {
   214  		return fmt.Errorf("compressor already registered on configurator for name %q", z.Name())
   215  	}
   216  	c.knownCompressors[z.Name()] = z
   217  	return nil
   218  }
   219  
   220  // MustRegisterCompressor registers the given compressor or panics.
   221  func (c *Configurator) MustRegisterCompressor(z transport.Compressor) {
   222  	if err := c.RegisterCompressor(z); err != nil {
   223  		panic(err)
   224  	}
   225  }
   226  
   227  // LoadConfigFromYAML loads a yarpc.Config from YAML data. Use LoadConfig if
   228  // you have already parsed a map[string]interface{} or
   229  // map[interface{}]interface{}.
   230  func (c *Configurator) LoadConfigFromYAML(serviceName string, r io.Reader) (yarpc.Config, error) {
   231  	b, err := ioutil.ReadAll(r)
   232  	if err != nil {
   233  		return yarpc.Config{}, err
   234  	}
   235  
   236  	var data map[string]interface{}
   237  	if err := yaml.Unmarshal(b, &data); err != nil {
   238  		return yarpc.Config{}, err
   239  	}
   240  	return c.LoadConfig(serviceName, data)
   241  }
   242  
   243  // LoadConfig loads a yarpc.Config from a map[string]interface{} or
   244  // map[interface{}]interface{}.
   245  //
   246  // See the module documentation for the shape the map[string]interface{} is
   247  // expected to conform to.
   248  func (c *Configurator) LoadConfig(serviceName string, data interface{}) (yarpc.Config, error) {
   249  	var cfg yarpcConfig
   250  	if err := config.DecodeInto(&cfg, data); err != nil {
   251  		return yarpc.Config{}, err
   252  	}
   253  	return c.load(serviceName, &cfg)
   254  }
   255  
   256  // NewDispatcherFromYAML builds a Dispatcher from the given YAML
   257  // configuration.
   258  func (c *Configurator) NewDispatcherFromYAML(serviceName string, r io.Reader) (*yarpc.Dispatcher, error) {
   259  	cfg, err := c.LoadConfigFromYAML(serviceName, r)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	return yarpc.NewDispatcher(cfg), nil
   264  }
   265  
   266  // NewDispatcher builds a new Dispatcher from the given configuration data.
   267  func (c *Configurator) NewDispatcher(serviceName string, data interface{}) (*yarpc.Dispatcher, error) {
   268  	cfg, err := c.LoadConfig(serviceName, data)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	return yarpc.NewDispatcher(cfg), nil
   273  }
   274  
   275  // Kit creates a dependency kit for the configurator, suitable for passing to
   276  // spec builder functions.
   277  func (c *Configurator) Kit(serviceName string) *Kit {
   278  	return &Kit{
   279  		name:     serviceName,
   280  		c:        c,
   281  		resolver: c.resolver,
   282  	}
   283  }
   284  
   285  func (c *Configurator) load(serviceName string, cfg *yarpcConfig) (_ yarpc.Config, err error) {
   286  	b := newBuilder(serviceName, c.Kit(serviceName))
   287  
   288  	for _, inbound := range cfg.Inbounds {
   289  		if e := c.loadInboundInto(b, inbound); e != nil {
   290  			err = multierr.Append(err, e)
   291  		}
   292  	}
   293  
   294  	for name, outboundConfig := range cfg.Outbounds {
   295  		if e := c.loadOutboundInto(b, name, outboundConfig); e != nil {
   296  			err = multierr.Append(err, e)
   297  		}
   298  	}
   299  
   300  	for name, attrs := range cfg.Transports {
   301  		if e := c.loadTransportInto(b, name, attrs); e != nil {
   302  			err = multierr.Append(err, e)
   303  		}
   304  	}
   305  
   306  	if e := c.validateLogging(cfg.Logging); e != nil {
   307  		err = multierr.Append(err, e)
   308  	}
   309  
   310  	if err != nil {
   311  		return yarpc.Config{}, err
   312  	}
   313  
   314  	yc, err := b.Build()
   315  	if err != nil {
   316  		return yc, err
   317  	}
   318  
   319  	cfg.Logging.fill(&yc)
   320  	cfg.Metrics.fill(&yc)
   321  	return yc, nil
   322  }
   323  
   324  func (c *Configurator) loadInboundInto(b *builder, i inbound) error {
   325  	if i.Disabled {
   326  		return nil
   327  	}
   328  
   329  	spec, err := c.spec(i.Type)
   330  	if err != nil {
   331  		// TODO: Maybe we should keep track of the inbound name so that if
   332  		// it differs from the transport name, we can mention that in the
   333  		// error message.
   334  		return fmt.Errorf("failed to load inbound: %v", err)
   335  	}
   336  
   337  	return b.AddInboundConfig(spec, i.Attributes)
   338  }
   339  
   340  func (c *Configurator) loadOutboundInto(b *builder, name string, cfg outbounds) error {
   341  	// This matches the signature of builder.AddImplicitOutbound,
   342  	// AddUnaryOutbound, AddOnewayOutbound and AddStreamOutbound
   343  	type adder func(*compiledTransportSpec, string, string, config.AttributeMap) error
   344  
   345  	loadUsing := func(o *outbound, adder adder) error {
   346  		spec, err := c.spec(o.Type)
   347  		if err != nil {
   348  			return fmt.Errorf("failed to load configuration for outbound %q: %v", name, err)
   349  		}
   350  
   351  		if err := adder(spec, name, cfg.Service, o.Attributes); err != nil {
   352  			return fmt.Errorf("failed to add outbound %q: %v", name, err)
   353  		}
   354  
   355  		return nil
   356  	}
   357  
   358  	if implicit := cfg.Implicit; implicit != nil {
   359  		return loadUsing(implicit, b.AddImplicitOutbound)
   360  	}
   361  
   362  	if unary := cfg.Unary; unary != nil {
   363  		if err := loadUsing(unary, b.AddUnaryOutbound); err != nil {
   364  			return err
   365  		}
   366  	}
   367  
   368  	if oneway := cfg.Oneway; oneway != nil {
   369  		if err := loadUsing(oneway, b.AddOnewayOutbound); err != nil {
   370  			return err
   371  		}
   372  	}
   373  
   374  	if stream := cfg.Stream; stream != nil {
   375  		if err := loadUsing(stream, b.AddStreamOutbound); err != nil {
   376  			return err
   377  		}
   378  	}
   379  
   380  	return nil
   381  }
   382  
   383  func (c *Configurator) loadTransportInto(b *builder, name string, attrs config.AttributeMap) error {
   384  	spec, err := c.spec(name)
   385  	if err != nil {
   386  		return fmt.Errorf("failed to load configuration for transport %q: %v", name, err)
   387  	}
   388  
   389  	return b.AddTransportConfig(spec, attrs)
   390  }
   391  
   392  // Returns the compiled spec for the transport with the given name or an error
   393  func (c *Configurator) spec(name string) (*compiledTransportSpec, error) {
   394  	spec, ok := c.knownTransports[name]
   395  	if !ok {
   396  		return nil, fmt.Errorf("unknown transport %q", name)
   397  	}
   398  	return spec, nil
   399  }
   400  
   401  // validateLogging validates if the given logging is valid or not.
   402  func (c *Configurator) validateLogging(l logging) error {
   403  	if (l.Levels.ApplicationError != nil || l.Levels.Failure != nil) && (l.Levels.ServerError != nil || l.Levels.ClientError != nil) {
   404  		return fmt.Errorf("invalid logging configuration, failure/applicationError configuration can not be used with serverError/clientError")
   405  	}
   406  
   407  	if (l.Levels.Outbound.ApplicationError != nil || l.Levels.Outbound.Failure != nil) && (l.Levels.Outbound.ServerError != nil || l.Levels.Outbound.ClientError != nil) {
   408  		return fmt.Errorf("invalid outbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError")
   409  	}
   410  
   411  	if (l.Levels.Inbound.ApplicationError != nil || l.Levels.Inbound.Failure != nil) && (l.Levels.Inbound.ServerError != nil || l.Levels.Inbound.ClientError != nil) {
   412  		return fmt.Errorf("invalid inbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError")
   413  	}
   414  
   415  	return nil
   416  }