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