admin: Add an API on Server to initiate a programmatic admin request.#3667
admin: Add an API on Server to initiate a programmatic admin request.#3667htuch merged 12 commits intoenvoyproxy:masterfrom
Conversation
Signed-off-by: Joshua Marantz <jmarantz@google.com>
Signed-off-by: Joshua Marantz <jmarantz@google.com>
Signed-off-by: Joshua Marantz <jmarantz@google.com>
Signed-off-by: Joshua Marantz <jmarantz@google.com>
include/envoy/buffer/buffer.h
Outdated
| * Construct a flattened string from a buffer. | ||
| * @return the flattened string. | ||
| */ | ||
| virtual std::string toString() const PURE; |
There was a problem hiding this comment.
Note: this method was previously implemented as a test utility; I made it slightly faster by right-sizing the string buffer first, and moving it into source/...
|
|
||
| namespace Envoy { | ||
| namespace Http { | ||
| namespace Utility { |
There was a problem hiding this comment.
Note: I changed this from a class with all static methods into a namespace in order to be able to extract QueryParams into type that was accessible from interfaces in include/...
Textually it looks like this whole file changed but that's just because it lost 2 chars of indent and a lot of 'static' keywords. Really it just lost QueryParams, which is now in include/envoy/http/query_params.h
There was a problem hiding this comment.
Thanks for the heads up, this is useful.
source/server/server.cc
Outdated
| Http::HeaderMapImpl response_headers; | ||
| std::string body; | ||
| Http::Code code = admin_->request(path_copy, params, method_copy, response_headers, body); | ||
| response_handler(code, response_headers, body); |
There was a problem hiding this comment.
Are you sure this is the pattern we want here? The callback is going to execute on some other thread (i.e. the main thread) to that of the caller. So, there will need to be some cross-thread synchronization happening. This has been a total nightmare in the FakeUpstream integration test code, and might lead to problematic main thread blocking.
An alternative would be to have the calling thread allocate a buffer, pass it to the post, wait using a CondVar for a response, and then take the buffer. That works nicely if the calling thread is not on an event loop. Alternatively, you post the response buffer to the event loop of the calling thread when done if async.
There was a problem hiding this comment.
per f2f:
- I will rename adminRequest to be adminRequestAsync, and add adminRequestBlocking, which will not use a function-pointer but block on the calling thread until the admin thread responds.
- I could also implement this by replacing the network socket with some other sort of local socket, such as socketpair(). I think that might be a similar amount of work or slightly less, but might present an OS porting barrier in the future.
There was a problem hiding this comment.
Why isn't this always just happening on the main thread? Do we really need to support post here at all?
There was a problem hiding this comment.
The caller will likely come from another thread, at least in our case.
There was a problem hiding this comment.
WDYT about handling the threading complexity in our private code then? I think for most people a use of this would be on the main thread...
There was a problem hiding this comment.
It sounds reasonable to have this PR just define Admin::request() requiring it be called from the main thread, so there's no complexity in this PR.
And I realize now that sever()->dispatcher()->post() provides all the infrastructure I'd need to call this from a different thread. I could make Admin::request() call DispatcherImpl::isThreadSafe() I suppose, if that were public, but if that's not generally going to happen I suppose it's sufficient to just document it.
I'm a little reluctant to do the post/block in this PR because that would tie up the calling thread, when the system we are merging it into might want to continue to do other work on that thread while the post is being processed.
include/envoy/server/instance.h
Outdated
| * @param path The admin request URL path as a string. | ||
| * @param params map of parameter names and values. | ||
| */ | ||
| virtual void adminRequest(absl::string_view path, const Http::Utility::QueryParams& params, |
There was a problem hiding this comment.
The server instance already exposes an Admin object. Seems like this isn't needed and should actually get routed through the admin object?
There was a problem hiding this comment.
Fair point, that shouldn simplify the PR. Thanks.
There was a problem hiding this comment.
It turns out not to dramatically simplify the PR as I just pushed the mocking boilerplate out of server and into admin :)
Signed-off-by: Joshua Marantz <jmarantz@google.com>
…d expose Admin::request() as an interface. Signed-off-by: Joshua Marantz <jmarantz@google.com>
| hooks_, restart_, stats_store_, fakelock_, component_factory_, | ||
| std::make_unique<NiceMock<Runtime::MockRandomGenerator>>(), thread_local_)), | ||
| EnvoyException, "unable to read file: ") | ||
| EnvoyException, "unable to read file: "); |
There was a problem hiding this comment.
I'm not sure how server_test.cc got checked in without the semi-colon, because without it, the auto-formatter scrambles this test-method, or the 'format' test should fail.
mattklein123
left a comment
There was a problem hiding this comment.
This approach LGTM. Will defer to someone else for detailed review.
|
|
||
| OwnedImpl::OwnedImpl(const void* data, uint64_t size) : OwnedImpl() { add(data, size); } | ||
|
|
||
| std::string OwnedImpl::toString() const { |
There was a problem hiding this comment.
How is this different to linearize()? Do we need both variants?
There was a problem hiding this comment.
I looked at linearize and though they seem similar, I don't see how either can be efficiently implemented on the other.
There was a problem hiding this comment.
well, you could linearize and then create a std;;string with the now linearized buffer. This would involve two copies, but is still O(n) and might be fine for the applications we would use this for, with the assumption being that you don't want to do toString() or linearize for anything performance sensitive anyway. There's also the option of just doing an absl::string_view around the linearized buffer.
There was a problem hiding this comment.
linearize mutates the buffer IIUC; I thought it made sense to have a simple observer that can return the fully flattened string.
Moreover it exists in the codebase now, in a test helper, and I'm mostly just moving it. However per your other comment -- I don't really need this in the interface, as everywhere in tests and in admin.cc where it's needed, I have an OwnedImpl. Removed.
There was a problem hiding this comment.
I think mutating is a benefit here, it's kind of like defragging. At least naively, it seems you should be able to implement it about as efficiently as the toString() operation.
|
|
||
| namespace Envoy { | ||
| namespace Http { | ||
| namespace Utility { |
There was a problem hiding this comment.
Thanks for the heads up, this is useful.
source/server/http/admin.h
Outdated
| const Http::StreamDecoderFilterCallbacks& getDecoderFilterCallbacks() const override; | ||
| const Http::HeaderMap& getRequestHeaders() const override; | ||
|
|
||
| static void populateFallbackResponseHeaders(Http::Code code, Http::HeaderMap& header_map); |
There was a problem hiding this comment.
Should this just be in the anonymous namespace in the .cc?
source/server/http/admin.cc
Outdated
| filter.decodeHeaders(request_headers, false); | ||
|
|
||
| // TODO(jmarantz): make QueryParams a real class and put this serializer there, | ||
| // along with proper URL escaping of the name and value. |
There was a problem hiding this comment.
TBH I think what I'd really like to do is change admin filter callbacks to take a const QueryParam& as an arg, rather than having each one parse it. Then serializing/parsing will be skipped and escaping will be unnecessary. Changing TODO. I don't want to tackle that in this PR though; will follow up.
| Http::HeaderMapImpl response_headers; | ||
| std::string body; | ||
| EXPECT_EQ(Http::Code::OK, | ||
| admin_.request("/stats", Http::Utility::QueryParams({{"format", "json"}}), "GET", |
There was a problem hiding this comment.
Probably should have a test for > 1 query param, since there is slightly different logic (switches to & separator).
There was a problem hiding this comment.
I couldn't find an admin handler that dealt with >1 query param, but I factored out the serialization to an http utility function and added tests for that to cover all the cases.
Signed-off-by: Joshua Marantz <jmarantz@google.com>
Signed-off-by: Joshua Marantz <jmarantz@google.com>
Signed-off-by: Joshua Marantz <jmarantz@google.com>
htuch
left a comment
There was a problem hiding this comment.
LGTM, but I'm still somewhat skeptical we need toString() added to the buffer virtual interface. Needs mater merge.
|
|
||
| OwnedImpl::OwnedImpl(const void* data, uint64_t size) : OwnedImpl() { add(data, size); } | ||
|
|
||
| std::string OwnedImpl::toString() const { |
There was a problem hiding this comment.
well, you could linearize and then create a std;;string with the now linearized buffer. This would involve two copies, but is still O(n) and might be fine for the applications we would use this for, with the assumption being that you don't want to do toString() or linearize for anything performance sensitive anyway. There's also the option of just doing an absl::string_view around the linearized buffer.
| EXPECT_EQ(0, buffer.length()); | ||
| } | ||
|
|
||
| TEST_F(OwnedImplTest, toString) { |
There was a problem hiding this comment.
ok, in this particular file most of the test methods start with lower-case letters, which may be more Envoyish? But lots of Envoy tests have test-methods starting with an upper-case letter. I'll just change all of them in this file to upper-case. SG?
| auto append = [&buffer](absl::string_view str) { buffer.add(str.data(), str.size()); }; | ||
| append("Hello, "); | ||
| append("world!"); | ||
| EXPECT_EQ("Hello, world!", buffer.toString()); |
There was a problem hiding this comment.
Should we have more corner cases for the conversion?
There was a problem hiding this comment.
Good question: what would you suggest? I actually don't know a ton about the underlying structure here.
There was a problem hiding this comment.
At least empty, single span, two spans..
… simplfy abstract Buffer interface. Signed-off-by: Joshua Marantz <jmarantz@google.com>
…OwnedImpl. Signed-off-by: Joshua Marantz <jmarantz@google.com>
Signed-off-by: Joshua Marantz <jmarantz@google.com>
| */ | ||
| static std::string bufferToString(const Buffer::Instance& buffer); | ||
| static std::string bufferToString(const Buffer::OwnedImpl& buffer) { | ||
| // TODO(jmarantz): remove this indirection and update all ~53 call sites. |
There was a problem hiding this comment.
@jmarantz +1 can we do this as a cleanup sooner rather than later? Thank you.
Description:
Risk Level: Medium
Testing: //test/...
Docs Changes: will be done with the PR that enables turning off admin.
Release Notes: N/A