Support for publicly accessible configuration parsing to support testing#88
Support for publicly accessible configuration parsing to support testing#88
Conversation
| EXPECT_EQ("", config.InstanceId()); | ||
| EXPECT_EQ("", config.InstanceZone()); | ||
| } | ||
| static void ParseConfiguration(MetadataAgentConfiguration& config, const std::string& input) { |
There was a problem hiding this comment.
Instead of having this helper, is it possible to just call std::stringstream inline throughout? This way other tests don't try to recycle this method.
There was a problem hiding this comment.
If not, I would vote for ParseConfigurationString, ParseConfigurationStream, and rename ParseConfig to ParseConfigurationFile.
| // value means that parsing succeeded, but all of the arguments were handled | ||
| // within the function and the program should exit with a success exit code. | ||
| int ParseArguments(int ac, char** av); | ||
| void ParseConfigurationStream(std::istream& input); |
There was a problem hiding this comment.
Instead, can we please just add a public constructor with a std::istream& parameter? Something like:
MetadataAgentConfiguration::MetadataAgentConfiguration(std::istream& input)
: MetadataAgentConfiguration() {
ParseConfiguration(input);
}and then construct the stream in the tests as:
const MetadataAgentConfiguration config(std::istringstream(
"ProjectId: TestProjectId\n"
"MetadataApiNumThreads: 13\n"
"MetadataReporterPurgeDeleted: true\n"
"MetadataReporterUserAgent: \"foobar/foobaz\"\n"
));?
There was a problem hiding this comment.
@igorpeshansky would using a constructor instead of a public method still allow us to dynamically reload configurations in the future? It would be nice if the metadata agent could pick up changes to the configuration on the disk and reload in the future, either via watching for changes on the file itself periodically, or by receiving a signal to reload the configuration. This could help us reduce the current negative impact of the metadata agent becoming unavailable during a restart, if the only intended change is a configuration update. Kubernetes already updates the files on disk when they're updated via ConfigMaps or Secrets, which would make this an awesome candidate. If you feel that a constructor / public method is just a matter of implementation, fine by me, as long as we are open to the possibility of dynamically reloading the configuration.
There was a problem hiding this comment.
Adding to this, I know both "allow us", but what are your thoughts on the topic as a whole, and which approach would be more idiomatic in your mind?
There was a problem hiding this comment.
I believe that such a functionality, when it does get added, should be encapsulated in the Configuration class. No external entity should be responsible for keeping the configuration up to date.
There was a problem hiding this comment.
@igorpeshansky I agree with that principle, however its not represented in the current implementation. Right now the configuration class has an empty constructor and requires the something external to call ParseArguments(int ac, char** av).
Perhaps we should have 2 constructors: MetadataAgentConfiguration(std::istream& input) and MetadataAgentConfiguration(int ac, char ** av)?
There was a problem hiding this comment.
I see, the problem is with error reporting. ParseArguments(int ac, char** av) needs to return the success mode to the caller. It does seem that this initialization needs to be done outside a constructor.
In the interest of moving things forward, I am going to implement the constructor as @igorpeshansky has described. Lets talk offline so I can better understand why this is the way to do it.
There was a problem hiding this comment.
Offline conclusion: We will make int ParseArguments(int ac, char** av) private, and int ::main(int ac, char** av); a friend of MetadataAgentConfiguration. This friendship indicates an encapsulation leak that will be refactored out at a later point.
igorpeshansky
left a comment
There was a problem hiding this comment.
Some stylistic things.
| #include <string> | ||
|
|
||
| namespace google { | ||
| int main(int ac, char** av); |
There was a problem hiding this comment.
You shouldn't need to pre-declare it here.
There was a problem hiding this comment.
I ran into a compilation issue without this forward declaration. Specifically: ‘int main(int, char**)’ should have been declared inside ‘::’
There was a problem hiding this comment.
Yeah, you're right, it needs to know about the main in the global namespace.
| namespace google { | ||
| int main(int ac, char** av); | ||
|
|
||
| namespace google { |
There was a problem hiding this comment.
Let's keep the blank line between the namespace and the class.
|
|
||
| void ParseConfigFile(const std::string& filename); | ||
| void ParseConfiguration(std::istream& input); | ||
| void ParseConfigurationStream(std::istream& input); |
There was a problem hiding this comment.
This renaming of private functions now seems gratuitous. Can we rename them back to their original names?
| EXPECT_EQ("limit=30", config.DockerContainerFilter()); | ||
| EXPECT_EQ(0, config.KubernetesUpdaterIntervalSeconds()); | ||
| EXPECT_EQ("https://kubernetes.default.svc", | ||
| config.KubernetesEndpointHost()); |
There was a problem hiding this comment.
Fits on the previous line.
| EXPECT_EQ("", config.InstanceResourceType()); | ||
| EXPECT_EQ(60, config.DockerUpdaterIntervalSeconds()); | ||
| EXPECT_EQ("unix://%2Fvar%2Frun%2Fdocker.sock/", | ||
| config.DockerEndpointHost()); |
There was a problem hiding this comment.
Fits on the previous line.
| VerifyDefaultConfig(); | ||
| TEST(MetadataAgentConfigurationTest, EmptyConfig) { | ||
| std::istringstream stream(""); | ||
| MetadataAgentConfiguration config(stream); |
There was a problem hiding this comment.
I would just inline the stream constructor, i.e.:
MetadataAgentConfiguration config(std::istringstream(""));There was a problem hiding this comment.
There seems to be some sort of compilation issue calling config with std::istringstream inline. Specifically: cannot bind non-const lvalue reference of type ‘std::istream& {aka std::basic_istream<char>&}’ to an rvalue of type ‘std::basic_istream<char>’
There was a problem hiding this comment.
Ah, we would need to add a delegating constructor with an rvalue reference:
class MetadataAgentConfiguration {
...
MetadataAgentConfiguration(std::istream&& input)
: MetadataAgentConfiguration(input) {}
...
};because a temporary std::istringstream is an rvalue reference, and we need to explicitly convert it to an lvalue.
| "MetadataReporterPurgeDeleted: true\n" | ||
| "MetadataReporterUserAgent: \"foobar/foobaz\"\n" | ||
| ); | ||
| MetadataAgentConfiguration config(stream); |
There was a problem hiding this comment.
I would just inline the stream constructor, i.e.:
MetadataAgentConfiguration config(std::istringstream(
"ProjectId: TestProjectId\n"
"MetadataApiNumThreads: 13\n"
"MetadataReporterPurgeDeleted: true\n"
"MetadataReporterUserAgent: \"foobar/foobaz\"\n"
));Also below.
|
|
||
| private: | ||
| friend class MetadataAgentConfigurationTest; | ||
| friend int ::main(int ac, char** av); |
There was a problem hiding this comment.
Let's add a comment on why it's a friend. You also shouldn't need the parameter names for a friend declaration. So, e.g.:
friend int ::main(int, char**); // Calls ParseArguments.WDYT?
| #include <string> | ||
|
|
||
| namespace google { | ||
| int main(int ac, char** av); |
There was a problem hiding this comment.
Yeah, you're right, it needs to know about the main in the global namespace.
| #include <mutex> | ||
| #include <string> | ||
|
|
||
| int main(int ac, char** av); |
There was a problem hiding this comment.
You can remove the parameter names here as well:
int main(int, char**);|
|
||
| void ParseConfigFile(const std::string& filename); | ||
| void ParseConfiguration(std::istream& input); | ||
| void ParseConfigFile(const std::string& filename); |
There was a problem hiding this comment.
Might as well keep them in the original order, too. 😄
| VerifyDefaultConfig(); | ||
| TEST(MetadataAgentConfigurationTest, EmptyConfig) { | ||
| std::istringstream stream(""); | ||
| MetadataAgentConfiguration config(stream); |
There was a problem hiding this comment.
Ah, we would need to add a delegating constructor with an rvalue reference:
class MetadataAgentConfiguration {
...
MetadataAgentConfiguration(std::istream&& input)
: MetadataAgentConfiguration(input) {}
...
};because a temporary std::istringstream is an rvalue reference, and we need to explicitly convert it to an lvalue.
igorpeshansky
left a comment
There was a problem hiding this comment.
LGTM ![]()
Don't forget to squash-and-merge.
…declaration of main
f5b5926 to
c0bf4ec
Compare
| MetadataAgentConfiguration config_; | ||
| }; | ||
|
|
||
| static const std::string IsolationPath(const std::string& test_name) { |
There was a problem hiding this comment.
const is superfluous here.
| MetadataAgentConfiguration config_; | ||
| }; | ||
|
|
||
| static const std::string IsolationPath(const std::string& test_name) { |
| MetadataAgentConfiguration config_; | ||
| }; | ||
|
|
||
| static const std::string IsolationPath(const std::string& test_name) { |
There was a problem hiding this comment.
Why not just have this return std::istringstream?
| // within the function and the program should exit with a success exit code. | ||
| int ParseArguments(int ac, char** av); | ||
| MetadataAgentConfiguration(std::istream& input); | ||
| MetadataAgentConfiguration(std::istream&& input) |
There was a problem hiding this comment.
I'm not sure what's happening here, why do we need two constructors?
edd9c4e to
7d08a61
Compare
8ead5cb to
72c3d58
Compare
| MetadataAgentConfiguration config_; | ||
| }; | ||
|
|
||
| static std::istringstream IsolationPathConfig(const std::string& test_name) { |
There was a problem hiding this comment.
Forgot this one. static is ok within a class, but for a free-standing function, it's preferred to have it in an anonymous namespace (style guide).
There was a problem hiding this comment.
static is no longer needed, right?
| MetadataAgentConfiguration config_; | ||
| }; | ||
|
|
||
| static std::istringstream IsolationPathConfig(const std::string& test_name) { |
There was a problem hiding this comment.
static is no longer needed, right?
No description provided.