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 }