github.com/koko1123/flow-go-1@v0.29.6/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/koko1123/flow-go-1/model/flow" 10 "github.com/koko1123/flow-go-1/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 }