Skip to content

build: making admin optional#23903

Merged
alyssawilk merged 10 commits intoenvoyproxy:mainfrom
alyssawilk:admin
Nov 16, 2022
Merged

build: making admin optional#23903
alyssawilk merged 10 commits intoenvoyproxy:mainfrom
alyssawilk:admin

Conversation

@alyssawilk
Copy link
Contributor

@alyssawilk alyssawilk commented Nov 8, 2022

Adding a build option to compile out Envoy's admin code.
The main API change is making the admin APIs OptRef instead of explicit references, which requires update to any code which adds config trackers for the admin console.

Risk Level: medium
Testing: new CI build with new flags
Docs Changes: n/a
Release Notes: not currently included
Platform Specific Features:
Fixes #23823

@repokitteh-read-only
Copy link

As a reminder, PRs marked as draft will not be automatically assigned reviewers,
or be handled by maintainer-oncall triage.

Please mark your PR as ready when you want it to be reviewed!

🐱

Caused by: #23903 was opened by alyssawilk.

see: more, trace.

Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Copy link
Contributor

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

88 files? My goodness :)

Looks great other than a few nits.

bazel/README.md Outdated
* http3/quic with --//bazel:http3=False
* autolinking libraries with --define=library_autolink=disabled
* admin HTML home page with `--define=admin_html=disabled`
* admin functionlity with `--define=admin_functionaity=disabled`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "admin_functionaity" => "admin_functionality", I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, thanks

ci/do_ci.sh Outdated

echo "Building and testing with wasm=wasmtime: ${TEST_TARGETS[*]}"
bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wasmtime "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only
echo "Building and testing with wasm=wasmtime: and admin_functionality disabled ${TEST_TARGETS[*]}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe mention admin_html too, for completeness?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"using_deprecated_config.yaml",
"**/*.template.yaml",
"freebind/freebind.yaml",
"envoy-tap-config.yaml",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why does this need to be added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the tap filter has admin functionality. I didn't go through the exercise of carving that out, the entire tap filter simply won't compile.
Because of that we exempt tap here and add it back below as long as admin isn't disabled

// ConfigTracker keys must be unique. We are asserting that no one has stolen the key
// from us, since the returned entry will be nullptr if the key already exists.
RELEASE_ASSERT(config_tracker_entry_, "");
if (admin.has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: early return

if (!admin.has_value()) {
  return;
}
...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use the ifdef?

Or do you also support (or plan to support) disabling admin at runtime via a runtime or command-line flag while still compiling it in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer most of the code compiling in both branches and minimal use of the ifdef. it means we can get coverage for more code for example

// from us, since the returned entry will be nullptr if the key already exists.
RELEASE_ASSERT(config_tracker_entry_, "");
: proto_traits_(proto_traits) {
if (admin.has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: early return

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string_view method,
const AdminRequestFn& handler) {
RELEASE_ASSERT(server_->admin().has_value(),
"adminRequest called with admin support compiled out");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd know better than I would, but I wonder if instead of RELEASE_ASSERT if we should just ASSERT() in debug, and return in release? But I suspect you already thought about this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we want to assert at all? Can't we just call the handler with an http error code? Maybe a 501?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a bug to call admin requests with them compiled out. I think we should definitely crash in debug mode at least.

"adding hystrix_event_stream endpoint to enable connection to hystrix dashboard");
admin.addHandler("/hystrix_event_stream", "send hystrix event stream",
MAKE_ADMIN_HANDLER(handlerHystrixEventStream), false, false);
if (server.admin().has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: early return

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/**
* Closes the listening socket for the admin.
*/
virtual void closeSocket() PURE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't seem to figure out why this new pure virtual method is needed. Oh is it because we're using std::unique_ptr<Admin> admin_ instead of std::unique_ptr<AdminImpl> admin_ in server.h? Do we need to make that change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do, or the server (which now has an API pointer because it may not know about the impl class) can't close the socket.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you, but I don't yet understand how to connect all the dots :) Why does the server need an API pointer now when before it had an impl pointer?

// Server::Instance
Admin& admin() override { return *admin_; }
OptRef<Admin> admin() override {
return makeOptRefFromPtr(reinterpret_cast<Envoy::Server::Admin*>(admin_.get()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised to see reinterpret_cast here instead of static_cast. Does static_cast work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparently - thought I tried that but must have been something else.

@@ -1,5 +1,7 @@
#include "envoy/extensions/http/header_formatters/preserve_case/v3/preserve_case.pb.h"

#include "source/extensions/filters/http/common/pass_through_filter.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, how did you find this to be missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build failed with admin disabled flags. because Envoy doesn't IWYU sometimes when you remove one include or library you have to add others in unexpected places.

void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string_view method,
const AdminRequestFn& handler) {
RELEASE_ASSERT(server_->admin().has_value(),
"adminRequest called with admin support compiled out");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we want to assert at all? Can't we just call the handler with an http error code? Maybe a 501?

#endif

#ifndef ENVOY_ADMIN_FUNCTIONALITY
#define DISABLE_IF_ADMIN_DISABLED return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: RETURN_IF_ADMIN_DISABLED may be more obvious at call-sites. If I read this in the middle of a function I might not know what DISABLE meant.

Also -- why not use this pattern in production code also? It's somewhat nice in a way :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use in production code because we don't always want returns in the production code
for tests I agree I prefer RETURN but I'm following the pattern. I'm inclined to do a follow-up and replace them all together, WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure makes sense.

// ConfigTracker keys must be unique. We are asserting that no one has stolen the key
// from us, since the returned entry will be nullptr if the key already exists.
RELEASE_ASSERT(config_tracker_entry_, "");
if (admin.has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use the ifdef?

Or do you also support (or plan to support) disabling admin at runtime via a runtime or command-line flag while still compiling it in?

Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>

void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string_view method,
const AdminRequestFn& handler) {
ASSERT(server_->admin().has_value(), "adminRequest called with admin support compiled out");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok having this assert in in debug mode seems OK. But if we are not in debug mode should we return a 501 or something?

Seems like the fall-through case is going to jus not populate response_headers at all which might be confusing.

Alternatively -- I know you are not going to like this, but this is really a C++ API for folks that instantiate main_common themselves and can call methods on it. You could ifdef out the existence of this method entirely so calling it would be a build failure rather than a crash. But it's up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love it, but I'll take it over a custom error path no one actually wants but I'd otherwise feel obliged to write tests for =P

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point!

Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
jmarantz
jmarantz previously approved these changes Nov 10, 2022
Copy link
Contributor

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, though I'm still curious about the AdminImpl => Admin change which necessitated the closeSocket method being added to the interface.

/**
* Closes the listening socket for the admin.
*/
virtual void closeSocket() PURE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you, but I don't yet understand how to connect all the dots :) Why does the server need an API pointer now when before it had an impl pointer?

Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
@alyssawilk alyssawilk dismissed stale reviews from RyanTheOptimist and jmarantz via 4a3f628 November 15, 2022 13:44
@alyssawilk
Copy link
Contributor Author

Before the server had an admin pointer because it knew about the admin class. With this change we stick the impl behind the "is admin compiled in" flags, so the class tracks the interface (since the impl may be compiled out) and the interface needs close socket. alternately we could stick close socket behind an ifdef and down cast from the interface to the impl, but this seemed much cleaner to me.

@alyssawilk
Copy link
Contributor Author

/retest

@repokitteh-read-only
Copy link

Retrying Azure Pipelines:
Check envoy-presubmit isn't fully completed, but will still attempt retrying.
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #23903 (comment) was created by @alyssawilk.

see: more, trace.

@RyanTheOptimist
Copy link
Contributor

Before the server had an admin pointer because it knew about the admin class. With this change we stick the impl behind the "is admin compiled in" flags, so the class tracks the interface (since the impl may be compiled out) and the interface needs close socket. alternately we could stick close socket behind an ifdef and down cast from the interface to the impl, but this seemed much cleaner to me.

AH! Yeah, ok that makes perfect sense (of course :>). Conditionally compiling would also seem fine but so does this. Thanks for the explanation. Still LGTM!

@alyssawilk alyssawilk enabled auto-merge (squash) November 15, 2022 15:27
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

allow compiling out the admin code

5 participants