-
Notifications
You must be signed in to change notification settings - Fork 103
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
Refactor metadata fetching into MetadataProvider
trait
#88
Conversation
/// `fetch_metadata` is the generic, top-level function that is used by the main | ||
/// function to fetch metadata. The configured provider is passed in and this | ||
/// function dispatches the call to the correct provider-specific fetch function | ||
pub fn fetch_metadata(provider: &str) -> Result<Metadata> { | ||
pub fn fetch_metadata(provider: &str) -> Result<Box<providers::MetadataProvider>> { |
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.
Why is the Box needed?
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.
Since we're returning multiple different concrete types that all implement MetadataProvider
, impl Trait
can't be used and we need a Box
to create an actual trait object.
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 thought that was the whole point of impl Trait
...
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.
Unfortunately not. From the RFC:
If a function returns impl Trait, its body can return values of any type that implements Trait, but all return values need to be of the same type.
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.
@csssuf for my own reference: pushing the -> impl Trait
one more level down too (i.e. returned by new()
), would that also not work?
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.
Nope. The compiler needs to be able to figure out static dispatch to use impl Trait
, so there's no way to trick it into doing the thing we want here.
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.
https://www.reddit.com/r/rust/comments/8sqiin/newbie_question_why_is_the_impl_and_dyn_in_the/ is also useful on this topic (there's various others too)
With rustc beta today for example, one can write:
diff --git a/src/lib.rs b/src/lib.rs
index 0f80d1c..2ec3af3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -90,7 +90,7 @@ macro_rules! box_result {
/// `fetch_metadata` is the generic, top-level function that is used by the main
/// function to fetch metadata. The configured provider is passed in and this
/// function dispatches the call to the correct provider-specific fetch function
-pub fn fetch_metadata(provider: &str) -> Result<Box<providers::MetadataProvider>> {
+pub fn fetch_metadata(provider: &str) -> Result<Box<dyn providers::MetadataProvider>> {
match provider {
"azure" => box_result!(azure::Azure::new()),
"cloudstack-metadata" => box_result!(cloudstack::network::CloudstackNetwork::new()),
Then it's explicitly clear the goal is dynamic dispatch.
BTW I love how much discussion there's been over a single heap allocation 😄
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.
without having looked into it at all, I wonder if it would be possible to simultaneously use dyn
and impl Trait
to do what we want without a heap allocation? mostly curious, the current version is totally fine by me too.
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.
No, they're mutually exclusive. We could avoid the Box
by using an enum as the function's return type, and essentially provide our own dynamic dispatch by implementing the trait for the enum, but IMO that's even less clean than simply returning a Box
ed trait object.
c52250e
to
89485a9
Compare
This is the first time I've ever looked at this code, and my Rust is beginner-intermediate-ish...so take my comments with that in mind. Overall, makes a lot of sense. A personal style thing, I would tend to use a single commit that adds and deletes code at once, but eh. I was about to ask about tests, but I found they exist just for EC2. I assume Kola will cover other cases? |
@cgwalters |
Fully testing for this basically means reproducing all API and quirks of all supported cloud providers, so the mocking is mostly assuring that it doesn't break at a very coarse level. It only covers EC2 mostly due to time limitations, and because real/subtle regressions will hopefully break the integration CI on the relevant cloud provider. |
src/providers/azure/mod.rs
Outdated
impl MetadataProvider for Azure { | ||
fn attributes(&self) -> Result<HashMap<String, String>> { | ||
let attributes = self.get_attributes()?; | ||
let mut out = HashMap::new(); |
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.
Here and in all the other providers too, what about with_capacity(2)
instead?
impl ::std::ops::Drop for ConfigDrive { | ||
fn drop(&mut self) { | ||
if self.temp_dir.is_some() { | ||
ConfigDrive::unmount(&self.path).unwrap(); |
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'm not sure we should panic here. (This is carried-over code, feel free to defer this)
@csssuf I did a cursory review of this, and it seems overall fine. Most of this is just moving functions around, I didn't dig into those. |
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.
lgtm after we get positive tests on all the major cloud providers (it might need a rebase on master, shouldn't be any conflicts though)
fn attributes(&self) -> Result<HashMap<String, String>> { | ||
let mut out = HashMap::with_capacity(3); | ||
|
||
let add_value = |map: &mut HashMap<_, _>, key: &str, name| -> Result<()> { |
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.
Unfortunate that there is so much duplication with the add_value
closure, but I'm not sure if there is a better way to do it after thinking through some of the alternatives.
Add a new trait `MetadataProvider` with functions for fetching each type of metadata coreos-metadata knows about, and helper functions for writing metadata to disk.
Add an implementation of `MetadataProvider` for each existing metadata provider.
Now that we expose an Ec2Provider struct in providers/ec2, use it in the ec2 mock tests.
Instead of fetching all possible metadata in fetch_metadata(), return a trait object which provides the same functions for writing to disk as the original `Metadata` struct, but only fetches metadata when necessary.
Drop the unused fetch_metadata() function from each provider, as the `MetadataProvider` trait is now used in their place.
`Metadata` is now unused after the switch to `MetadataProvider`, so drop it.
Verified that the refactor's output matches the current release's (but without the unnecessary requests) on:
|
Instead of
fetch_metadata()
fetching all potentially needed metadata, return aMetadataProvider
trait object which has drop-in replacements forMetadata
's functions. This allows only the requested metadata types to be fetched and avoids potentially expensive fetching work.There's still a little bit of cleanup work that could take place in some of the providers (mostly rolling now-duplicate functions into the trait implementation), but those seemed like they should wait for another PR. This PR contains roughly the minimal amount of refactoring necessary to switch over to the
MetadataProvider
trait.Fixes coreos/bugs#2362.