Add a reloadable objects registry#8205
Conversation
This PR also unifies reload mechanism to be used by central management
0f90819 to
9b8f9a4
Compare
| register.RLock() | ||
| defer register.RUnlock() | ||
| return register.confsLists[name] | ||
| } |
There was a problem hiding this comment.
@exekias Can it be possible to not make it a package variable and instead contains everything within the type/instance of the type? I don't know all the implementation details but as an information we are trying to remove any package variable and global variable from the core so when we see them we always ask do we need really need them?
There was a problem hiding this comment.
That's an option, but in practice, that would mean we need to pass an instance of the registry to all the running parts of the beat. Is there any plan to introduce something like pkg/context for this?
There was a problem hiding this comment.
@exekias we want to introduce this, but we are a bit far from that, I am toying with the idea in beatless.
Let me clarify, when I first read your PR, I thought why not use the global feature registry to hold that and just wrap it in some function calls to do the type assertions, this is what I am doing in the beatless PR.
But after taking a step back I thought it's probably better to have this specific registry which deal with alive config changes. But I am not sure how you want to structure it with the application but I would expect some kind of listener that will trigger events? Maybe a bit of details or pointers of the usage would help me?
With that in mind I think what we can do without introducing a pkg/context is to make sure that the registry you create is instead a type that your Registry variable instantiate, this is what you almost do.
But instead of exposing the internals of the registry in the exposed function like the following you do like the next example which hide the implementation details but provide package accessable function. Will help if you add more logic to the class to be able to unit test it without having side effects on the running code.
func MustRegister(name string, r Reloadable) {
register.Lock()
defer register.Unlock()
if r == nil {
panic("Got a nil object")
}
if _, ok := register.confs[name]; ok {
panic(fmt.Sprintf("%s configuration is already registered", name))
}
register.confs[name] = r
}
var Registry ...
func MustRegister(name string, r Reloadable) {
err := Registry.Register(name, r)
if err != nil {
panic(".... something bad happened report the error.")
}
}
There was a problem hiding this comment.
I think I got it, will do the changes! Thanks for your input 👍
libbeat/common/reload/reload_test.go
Outdated
| MustRegister("name", nil) | ||
| }) | ||
|
|
||
| assert.Panics(t, func() { |
libbeat/common/reload/reload.go
Outdated
| defer register.Unlock() | ||
|
|
||
| if r == nil { | ||
| panic("Got a nil object") |
There was a problem hiding this comment.
What about returning an error instead?
There was a problem hiding this comment.
All the register calls should happen while initializing the beat, if an error happens here it means we did something really wrong, it would be interesting to know that early, I think we follow this pattern in many other registries?
There was a problem hiding this comment.
ok, lets follow the common pattern.
There was a problem hiding this comment.
It's good practice to offer both workflow.
There was a problem hiding this comment.
I added another commit to offer both in a non-global fashion
libbeat/common/reload/reload.go
Outdated
| } | ||
|
|
||
| if _, ok := register.confs[name]; ok { | ||
| panic(fmt.Sprintf("%s configuration is already registered", name)) |
There was a problem hiding this comment.
What about returning an error instead?
libbeat/common/reload/reload.go
Outdated
| defer register.Unlock() | ||
|
|
||
| if list == nil { | ||
| panic("Got a nil object") |
There was a problem hiding this comment.
See above, same for next panic.
libbeat/common/reload/reload.go
Outdated
| defer r.Unlock() | ||
|
|
||
| if r == nil { | ||
| return errors.New("Got a nil object") |
402ead9 to
0372531
Compare
|
Thanks everyone for the reviews, I think this is ready for a second look! |
libbeat/common/reload/reload_test.go
Outdated
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| type nonreloadable struct{} |
| func newRegistry() *registry { | ||
| return ®istry{ | ||
| confsLists: make(map[string]ReloadableList), | ||
| confs: make(map[string]Reloadable), |
There was a problem hiding this comment.
Btw, is there a reason to have two interfaces, one for a single entity and another one for lists of entities?
Methods could accept config ...*ConfigWithMeta and single entities could be stored as lists of one element.
Also, is it expected to have entities and list of entities with the same name?
There was a problem hiding this comment.
I don't expect entities sharing the name, these should map to a config key, like metricbeat.modules or output.
The problem with having a single definition is that then, the objects have to handle possible errors in the Reload call. Like for instance, having the output object receiving a list of configs. This split is semantic, as the two entities behave differently.
There was a problem hiding this comment.
The suggestion from @jsoriano has some values, because currently we could call Register and RegisterList with the same name (same map key). Having a single interface for both concept would mean that we only have one map.
* Add a reloadable objects registry (cherry picked from commit 4d6d1eb)
This change allows us to register reloadable blocks of configurations while instantiating them. Central management will use this registry to retrieve reloadable blocks and handle their configs.