github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/updatable_configs/manager.go (about)

     1  package updatable_configs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/module/util"
    11  )
    12  
    13  // ErrAlreadyRegistered is returned when a config field is registered with a name
    14  // conflicting with an already registered config field.
    15  var ErrAlreadyRegistered = fmt.Errorf("config name already registered")
    16  
    17  // ValidationError is returned by a config setter function (Set*ConfigFunc) when
    18  // the provided config field is invalid, and was not applied.
    19  type ValidationError struct {
    20  	Err error
    21  }
    22  
    23  func (err ValidationError) Error() string {
    24  	return err.Err.Error()
    25  }
    26  
    27  func NewValidationErrorf(msg string, args ...any) ValidationError {
    28  	return ValidationError{
    29  		Err: fmt.Errorf(msg, args...),
    30  	}
    31  }
    32  
    33  func IsValidationError(err error) bool {
    34  	return errors.As(err, &ValidationError{})
    35  }
    36  
    37  type (
    38  	SetAnyConfigFunc func(any) error
    39  	GetAnyConfigFunc func() any
    40  )
    41  
    42  // The below are typed setter and getter functions for different config types.
    43  //
    44  // ADDING A NEW TYPE:
    45  // If you need to add a new configurable config field with a type not below:
    46  //  1. Add a new setter and getter type below
    47  //  2. Add a Register*Config method to the Registrar interface and Manager implementation below
    48  //  3. Add a TestManager_Register*Config test to the manager_test.go file.
    49  
    50  type (
    51  	// Set*ConfigFunc is a setter function for a single updatable config field.
    52  	// Returns ValidationError if the new config value is invalid.
    53  
    54  	SetUintConfigFunc           func(uint) error
    55  	SetBoolConfigFunc           func(bool) error
    56  	SetDurationConfigFunc       func(time.Duration) error
    57  	SetIdentifierListConfigFunc func(flow.IdentifierList) error
    58  
    59  	// Get*ConfigFunc is a getter function for a single updatable config field.
    60  
    61  	GetUintConfigFunc           func() uint
    62  	GetBoolConfigFunc           func() bool
    63  	GetDurationConfigFunc       func() time.Duration
    64  	GetIdentifierListConfigFunc func() flow.IdentifierList
    65  )
    66  
    67  // Field represents one dynamically configurable config field.
    68  type Field struct {
    69  	// Name is the name of the config field, must be globally unique.
    70  	Name string
    71  	// TypeName is a human-readable string defining the expected type of inputs.
    72  	TypeName string
    73  	// Set is the setter function for the config field. It enforces validation rules
    74  	// and applies the new config value.
    75  	// Returns ValidationError if the new config value is invalid.
    76  	Set SetAnyConfigFunc
    77  	// Get is the getter function for the config field. It returns the current value
    78  	// for the config field.
    79  	Get GetAnyConfigFunc
    80  }
    81  
    82  // Manager manages setter and getter for updatable configs, across all components.
    83  // Components register updatable config fields with the manager at startup, then
    84  // the Manager exposes the ability to dynamically update these configs while the
    85  // node is running, for example, via admin commands.
    86  //
    87  // The Manager maintains a list of type-agnostic updatable config fields. The
    88  // typed registration function (Register*Config) is responsible for type conversion.
    89  // The registration functions must convert input types (as parsed from JSON) to
    90  // the Go type expected by the config field setter. They must also convert Go types
    91  // from config field getters to displayable types (see structpb.NewValue for details).
    92  type Manager struct {
    93  	mu     sync.Mutex
    94  	fields map[string]Field
    95  }
    96  
    97  func NewManager() *Manager {
    98  	return &Manager{
    99  		fields: make(map[string]Field),
   100  	}
   101  }
   102  
   103  // GetField returns the updatable config field with the given name, if one exists.
   104  func (m *Manager) GetField(name string) (Field, bool) {
   105  	m.mu.Lock()
   106  	defer m.mu.Unlock()
   107  	field, ok := m.fields[name]
   108  	return field, ok
   109  }
   110  
   111  // AllFields returns all currently registered fields.
   112  func (m *Manager) AllFields() []Field {
   113  	m.mu.Lock()
   114  	defer m.mu.Unlock()
   115  	fields := make([]Field, 0, len(m.fields))
   116  	for _, field := range m.fields {
   117  		fields = append(fields, field)
   118  	}
   119  	return fields
   120  }
   121  
   122  var _ Registrar = (*Manager)(nil)
   123  
   124  // Registrar provides an interface for registering config fields which can be
   125  // dynamically updated while the node is running.
   126  // Configs must have globally unique names. Setter functions are responsible for
   127  // enforcing component-specific validation rules, and returning a ValidationError
   128  // if the new config value is invalid.
   129  type Registrar interface {
   130  	// RegisterBoolConfig registers a new bool config.
   131  	// Returns ErrAlreadyRegistered if a config is already registered with name.
   132  	RegisterBoolConfig(name string, get GetBoolConfigFunc, set SetBoolConfigFunc) error
   133  	// RegisterUintConfig registers a new uint config.
   134  	// Returns ErrAlreadyRegistered if a config is already registered with name.
   135  	RegisterUintConfig(name string, get GetUintConfigFunc, set SetUintConfigFunc) error
   136  	// RegisterDurationConfig registers a new duration config.
   137  	// Returns ErrAlreadyRegistered if a config is already registered with name.
   138  	RegisterDurationConfig(name string, get GetDurationConfigFunc, set SetDurationConfigFunc) error
   139  	// RegisterIdentifierListConfig registers a new []Identifier config
   140  	// Returns ErrAlreadyRegistered if a config is already registered with name.
   141  	RegisterIdentifierListConfig(name string, get GetIdentifierListConfigFunc, set SetIdentifierListConfigFunc) error
   142  }
   143  
   144  // RegisterBoolConfig registers a new bool config.
   145  // Setter inputs must be bool-typed values.
   146  // Returns ErrAlreadyRegistered if a config is already registered with name.
   147  func (m *Manager) RegisterBoolConfig(name string, get GetBoolConfigFunc, set SetBoolConfigFunc) error {
   148  	m.mu.Lock()
   149  	defer m.mu.Unlock()
   150  
   151  	if _, exists := m.fields[name]; exists {
   152  		return fmt.Errorf("can't register config %s: %w", name, ErrAlreadyRegistered)
   153  	}
   154  
   155  	field := Field{
   156  		Name:     name,
   157  		TypeName: "bool",
   158  		Get: func() any {
   159  			return get()
   160  		},
   161  		Set: func(val any) error {
   162  			bval, ok := val.(bool)
   163  			if !ok {
   164  				return NewValidationErrorf("invalid type for bool config: %T", val)
   165  			}
   166  			return set(bval)
   167  		},
   168  	}
   169  	m.fields[field.Name] = field
   170  	return nil
   171  }
   172  
   173  // RegisterUintConfig registers a new uint config.
   174  // Setter inputs must be float64-typed values and will be truncated if not integral.
   175  // Returns ErrAlreadyRegistered if a config is already registered with name.
   176  func (m *Manager) RegisterUintConfig(name string, get GetUintConfigFunc, set SetUintConfigFunc) error {
   177  	m.mu.Lock()
   178  	defer m.mu.Unlock()
   179  
   180  	if _, exists := m.fields[name]; exists {
   181  		return fmt.Errorf("can't register config %s: %w", name, ErrAlreadyRegistered)
   182  	}
   183  
   184  	field := Field{
   185  		Name:     name,
   186  		TypeName: "uint",
   187  		Get: func() any {
   188  			return get()
   189  		},
   190  		Set: func(val any) error {
   191  			fval, ok := val.(float64) // JSON numbers always parse to float64
   192  			if !ok {
   193  				return NewValidationErrorf("invalid type for bool config: %T", val)
   194  			}
   195  			return set(uint(fval))
   196  		},
   197  	}
   198  	m.fields[field.Name] = field
   199  	return nil
   200  }
   201  
   202  // RegisterDurationConfig registers a new duration config.
   203  // Setter inputs must be duration-parseable string-typed values.
   204  // Returns ErrAlreadyRegistered if a config is already registered with name.
   205  func (m *Manager) RegisterDurationConfig(name string, get GetDurationConfigFunc, set SetDurationConfigFunc) error {
   206  	m.mu.Lock()
   207  	defer m.mu.Unlock()
   208  
   209  	if _, exists := m.fields[name]; exists {
   210  		return fmt.Errorf("can't register config %s: %w", name, ErrAlreadyRegistered)
   211  	}
   212  
   213  	field := Field{
   214  		Name:     name,
   215  		TypeName: "duration",
   216  		Get: func() any {
   217  			val := get()
   218  			return val.String()
   219  		},
   220  		Set: func(val any) error {
   221  			sval, ok := val.(string)
   222  			if !ok {
   223  				return NewValidationErrorf("invalid type for duration config: %T", val)
   224  			}
   225  			dval, err := time.ParseDuration(sval)
   226  			if err != nil {
   227  				return NewValidationErrorf("unparseable duration: %s: %w", sval, err)
   228  			}
   229  			return set(dval)
   230  		},
   231  	}
   232  	m.fields[field.Name] = field
   233  	return nil
   234  }
   235  
   236  // RegisterIdentifierListConfig registers a new []Identifier config
   237  // Setter inputs must be []any-typed values, with string elements parseable as Identifier.
   238  // Returns ErrAlreadyRegistered if a config is already registered with name.
   239  func (m *Manager) RegisterIdentifierListConfig(name string, get GetIdentifierListConfigFunc, set SetIdentifierListConfigFunc) error {
   240  	m.mu.Lock()
   241  	defer m.mu.Unlock()
   242  
   243  	if _, exists := m.fields[name]; exists {
   244  		return fmt.Errorf("can't register config %s: %w", name, ErrAlreadyRegistered)
   245  	}
   246  
   247  	field := Field{
   248  		Name:     name,
   249  		TypeName: "IdentifierList",
   250  		Get: func() any {
   251  			return util.DetypeSlice(get().Strings())
   252  		},
   253  		Set: func(val any) error {
   254  			gval, ok := val.([]any)
   255  			if !ok {
   256  				return NewValidationErrorf("invalid type for IdentifierList config: %T", val)
   257  			}
   258  			ids := make(flow.IdentifierList, len(gval))
   259  			for i, gid := range gval {
   260  				sid, ok := gid.(string)
   261  				if !ok {
   262  					return NewValidationErrorf("invalid element type %T for IdentifierList config - should be string", gid)
   263  				}
   264  				id, err := flow.HexStringToIdentifier(sid)
   265  				if err != nil {
   266  					return NewValidationErrorf("un-parseable id %s found in list: %w", gid, err)
   267  				}
   268  				ids[i] = id
   269  			}
   270  			return set(ids)
   271  		},
   272  	}
   273  	m.fields[field.Name] = field
   274  	return nil
   275  }