-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Notify extensions of the Collector's effective configuration #6833
Notify extensions of the Collector's effective configuration #6833
Conversation
97a0a3c
to
f89aced
Compare
f89aced
to
b992a66
Compare
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #6833 +/- ##
==========================================
- Coverage 90.74% 90.57% -0.17%
==========================================
Files 300 301 +1
Lines 15164 15347 +183
==========================================
+ Hits 13760 13900 +140
- Misses 1123 1161 +38
- Partials 281 286 +5
☔ View full report in Codecov by Sentry. |
This PR was marked stale due to lack of activity. It will be closed in 14 days. |
9b1da88
to
64af732
Compare
@bogdandrutu This should be ready for another look. |
otelcol/config.go
Outdated
@@ -60,6 +61,9 @@ type Config struct { | |||
Extensions map[component.ID]component.Config | |||
|
|||
Service service.Config | |||
|
|||
// Conf is the confmap.Conf used to create this Config | |||
Conf *confmap.Conf |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not ok, this is not parsed from the config, you break the design here since everything else is parsed from the config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, it's not ideal. I've pushed a commit that makes this field a method to separate it from the public fields on this struct. Let me know what you think.
If we don't want the source confmap.Conf
struct available from Config
, I see two possibilities:
- We could change the
ConfigProvider.Get
method to also return*confmap.Conf
. To me, this muddies the method signature a little bit, but would provide a clear separation between the two types. - We could add a new method to
ConfigProvider
to only resolve and return aconfmap.Conf
struct. I opted against this since we are already resolving the config inGet
and I think another method would be unnecessary on theConfigProvider
interface, but this would also allow clearly separatingconfmap.Conf
andConfig
.
Let me know if there's another option I've missed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bogdandrutu I'm sorry, I think I misunderstood what you meant.
If we are looking to have both Config
and ConfigProvider
be agnostic of the underlying configuration structure, then I see a few options:
- Provide an optional interface for config providers to implement that resolves the configuration and translates it (if necessary) to a
confmap.Conf
object. This seems like it would probably be the most straightforward solution. - Provide another type that can be marshaled into YAML from either the
Config
orConfigProvider
types that is used inextension.ConfigWatcher
, in place ofconfmap.Conf
. - Provide a way to translate
Config
instances back intoconfmap.Conf
.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bogdandrutu I've pushed a commit including a rough draft for an optional interface to provide confmap.Conf
objects from a Config Provider. Let me know what you think.
3d2108e
to
cf055fa
Compare
This PR was marked stale due to lack of activity. It will be closed in 14 days. |
@bogdandrutu PTAL. |
892b880
to
ea7c1c3
Compare
@dmitryax Your review would be greatly appreciated to get this closer to the finish line. I will try to summarize the discussion so far, I'd welcome your input on these points. Getting the config The extension needs a serialized version of the Collector's effective configuration, which we determined is best provided as a Communicating the config We want to provide an optional interface to facilitate communicating the effective config from the Host to Extensions. The communication flow will look slightly differently depending on what interface we provide. We can provide an interface to be implemented by the Host, e.g. with a
We can also provide an interface for Extensions, which the Host will query for and "push" the config to the Extensions. This is the approach currently implemented in this PR. I think this approach has the following benefits:
Overall providing an interface for extensions opens the possibilities for additional functionality in the future while not being much more complex. However, I think both approaches would work fine and the differences between them aren't substantial right now. |
c7d7187
to
fcf1785
Compare
Bogdan is out for a while. Removing the block since I believe his comments were addressed.
otelcol/configprovider.go
Outdated
// ConfmapProvider is an optional interface to be implemented by ConfigProviders | ||
// to provide confmap.Conf objects representing a marshaled version of the | ||
// Collector's configuration. | ||
type ConfmapProvider interface { | ||
// GetConfmap resolves the Collector's configuration and provides it as a confmap.Conf object. | ||
// | ||
// Should never be called concurrently with itself or any ConfigProvider method. | ||
GetConfmap(ctx context.Context) (*confmap.Conf, error) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the only place we use it is in this package. Is it intended to have other implementations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also noticed that we already have another interface called confmap.Provider
, which seems odd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal of the interface was to not require structs implementing ConfigProvider
to have any dependencies on confmap.Conf
. As for the name, I agree that it is a bit unfortunate. I am open to suggestions here, my intent was to signify that otelcol.ConfigProvider
provides Config
structs while otelcol.ConfmapProvider
provides confmap.Conf
structs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal of the interface was to not require structs implementing ConfigProvider to have any dependencies on confmap.Conf.
I am probably missing something, but isn't configProvider
struct in this same file the only implementation of ConfigProvider
? And the configProvider
struct in this same PR adds implementation of GetConfmap()
func to configProvider
struct , so it does take dependency confmap.Conf
, right?
Are there are other implementations of ConfigProvider
elsewhere which will not implement GetConfmap()
func?
Which "structs implementing ConfigProvider" do you refer to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current design is in response to this feedback: #6833 (comment). I realize the immediate comment is only referring to putting confmap.Conf
as a field on Config
, but the deeper implication to me seemed to be that ConfigProvider
shouldn't depend on any underlying data structure. It's possible I've misinterpreted the feedback.
I'll try to answer your questions by explaining my understanding of the situation and how my approach fits in.
As far as I'm aware, configProvider
is the only implementation of ConfigProvider
in any upstream Collector repository. Furthermore, it is only currently used inside the package where it is defined. However, the existence of ConfigProvider
as a public interface implies that anyone who wants to reimplement this part of the Collector can provide their own implementation. At present, configProvider
implements ConfigProvider
using confmap.Conf
as its underlying format, but ConfigProvider
doesn't specify any usage of confmap.Conf
. Anyone who reimplements this part of the Collector could pass otelcol.NewCollector
a ConfigProvider
that knows nothing about confmap.Conf
.
This works, but looking at it again now that I've spent a lot more time in this part of the code, I see a few things I don't like about this approach:
- It's not clear why someone would want to avoid
confmap.Conf
when it is used across the rest of the Collector. - Interacting with
confmap.Conf
is required for the rest of the functionality in this PR, so realistically it's necessary regardless. - As pointed out here and here, the configuration represented by
Config
andconfmap.Conf
may not be in sync.
I would still argue that my original approach of providing Config.ToConfmap
is probably the best approach. I think it makes the most sense to marshal data directly from the data itself, and the best way to do that here would be to store the marshaled representation on Config
. confmap.Conf
offers this with NewFromStringMap
and Conf.ToStringMap
, so there is also precedence for this design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear why someone would want to avoid confmap.Conf when it is used across the rest of the Collector.
+1. I don't understand what problem the avoidance solves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make another thorough review tomorrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can provide an interface to be implemented by the Host, e.g. with a GetConfig method, and have Extensions "pull" the configuration.
@evan-bradley have you considered an optional host interface with a function taking a callback that extensions can provide to be notified? With that, we should get the "push" model
I did think about this a little, I should have called this out as an option when summarizing possible approaches. For the most part this is functionally equivalent to an interface for extensions. Where I think it would get more complex is in the case where a Collector distro has a custom service that supports some variation of hot reloading, we would need to provide a mechanism for registering and unregistering callbacks. I've looked at #6550 and #6560 for ideas on how the two might compare. Overall, I think the interface for extensions ends up feeling conceptually simpler since it's just a method call and doesn't require any additional structures (callback lists, message buses, etc.). |
Why would we need unregistering of callbacks?
With the callback, I think we solve the problem pointed out in #6833 (comment). We won't need
What other additional structures do you think we need? Maybe I miss something. |
I apologize in advance for the wall of text, but unfortunately these issues are really about the details.
If someone were to implement a service that allows reloading an extension only when its configuration changes, they would need to unregister callbacks for extensions that will be restarted. Thinking on it more though, the Host could handle this itself, so I think we are good here.
I would expect the interface to look something more like the following, with type ConfmapNotifier interface {
Subscribe(func(context.Context, *confmap.Conf) error) error
}
It's not a big deal, but with an interface for extensions, we just loop through the service's set of extensions and query for the interface. With callbacks, we have to keep a list/map of callbacks and maintain it as extensions subscribe, and if a custom Service implements it, as they are hot reloaded.
I see how it would replace We need to pull the config out of
We could also use a callback here, but I feel like we shouldn't need to since As for #6833 (comment), the core of the issue as I see it is passing two representations of the config across public API boundaries, I only see two solutions here:
|
This PR was marked stale due to lack of activity. It will be closed in 14 days. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good solution. I played with it and didn't find any better way to expose this functionality.
@djaglowski, please take if it's good to go from your perspective |
Description:
This adds an optional
ConfigWatcher
interface that extensions can implement if they want to be notified of the effective configuration that is used by the Collector.I don't feel very strongly about any of the decisions I made in this PR, so I am open to input if we would like to take a different approach anywhere. I will leave some comments to explain the decisions I made.
Link to tracking Issue:
Closes #6596
Testing:
I've made minimal unit test changes, but I expect to write more tests once there is consensus on the direction for implementing this functionality. I have done some manual testing to show that an extension can get a YAML representation of the effective config using two YAML input files.