Skip to content

router: support dynamic host metadata headers#2179

Merged
zuercher merged 17 commits intoenvoyproxy:masterfrom
turbinelabs:impl-2148-dynamic-host-headers
Dec 13, 2017
Merged

router: support dynamic host metadata headers#2179
zuercher merged 17 commits intoenvoyproxy:masterfrom
turbinelabs:impl-2148-dynamic-host-headers

Conversation

@zuercher
Copy link
Copy Markdown
Member

@zuercher zuercher commented Dec 9, 2017

Modifies the Router::RequestInfoHeaderFormatter to support extracting string, numeric, and boolean values from the upstream host provided by AccessLog::RequestInfo.

Modifies Router::HeaderParser to omit headers whose formatted value is an empty string.

Risk Level: Low

Testing:
Unit tests to cover parsing of new header variable expression.

Docs Changes:
envoyproxy/data-plane-api#335

Release Notes:
Updated RAW_RELEASE_NOTES.md

Fixes #2148.

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@zuercher
Copy link
Copy Markdown
Member Author

zuercher commented Dec 9, 2017

I wanted to update test/integration/header_integration_test.cc to test these, but it seems pretty tricky to use the existing config modifiers to set up an EDS cluster (which is required to get endpoint metadata). Am I missing something?

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@mattklein123
Copy link
Copy Markdown
Member

I wanted to update test/integration/header_integration_test.cc to test these, but it seems pretty tricky to use the existing config modifiers to set up an EDS cluster (which is required to get endpoint metadata). Am I missing something?

For an integration test, you would need to also setup a fake EDS server. Harvey did a bunch of work to get an ADS integration test working: https://github.com/envoyproxy/envoy/blob/master/test/integration/ads_integration_test.cc. If you want to do an integration test you could build on that and it would probably be pretty straightforward.

namespace {

// Parse list of namespace and keys into a vector. Supports backslash escaping.
std::vector<std::string> parseUpstreamMetadataParams(const std::string& field) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Before doing a full review we should consider @htuch comment in the data-plane-api PR as to whether we potentially want to use embedded JSON here or something so we could use the JSON parser. Seems like a good idea to me to use JSON perhaps, but I could go either way. Thoughts?

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@mattklein123
Copy link
Copy Markdown
Member

@zuercher can you look at TSAN? I can take a look after that is fixed.

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@zuercher
Copy link
Copy Markdown
Member Author

For reference, the TSAN error occurs when some field descriptor in the DiscoveryRequest hasn't been fully initialized -- the protobuf lib initializes it using what looks to be an atomicity-based mutex-free algorithm, but apparently TSAN doesn't like it. I believe the offending address is the atomically-accessed guard field.

In any event, my fix is to initialize the field descriptors by parsing the message from YAML, which is probably easier to read than the C++ API version I had before.

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Great stuff. Few small comments. @alyssawilk do you mind taking a look at the test stuff? Might be best for your other change to merge if we are going to do other base test changes.

return request_info.getDownstreamAddress();
};
} else if (StringUtil::startsWith(field_name.c_str(), "UPSTREAM_METADATA")) {
field_extractor_ = parseUpstreamMetadataField(field_name.substr(17));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: Might use sizeof() here or some other constant to avoid the magic '17'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

"Invalid value.\n");

// No parameters.
EXPECT_THROW(RequestInfoHeaderFormatter requestInfoHeaderFormatter("UPSTREAM_METADATA", false),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible to use EXPECT_THROW_WITH_MESSAGE for all of these?

)EOF"));

// Give the config utility a place to stuff the upstream host's port (must come before
// the eds-cluster to keep the upstreams and ports in the same order).)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: trailing ')'

EXPECT_EQ(expected_headers, headers);
}

bool use_eds_{0};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: I would use '{}' or '{false}' here vs. 0

api_type: GRPC
)EOF"));

// Give the config utility a place to stuff the upstream host's port (must come before
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a little confusing. Is this just because of how the default config stuff works? Would it be worth it to build this EDS stuff into the base integration class? It seems generally useful and very cool. cc @alyssawilk

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, I think the same way that we special cased the HttpConnectionManager it'd be useful to have utilities to play with EDS config directly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I can take a look at making the config smarter about EDS in another review, if you'd like.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you want to do a follow up / TODO that's fine, but can you add more of a comment here? It's confusing why the placeholder is necessary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

But yes, it's to do with ConfigHelper expecting the number of ports assigned to match the number of upstreams during the call to finalize.


std::function<std::string(const Envoy::AccessLog::RequestInfo&)>
parseUpstreamMetadataField(const std::string& params_str) {
if (params_str.empty() || params_str[0] != '(' || params_str[params_str.size() - 1] != ')') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if (params_str.empty() || params_str.front() != '(' || params_str.back() != ')')

return [params](const Envoy::AccessLog::RequestInfo& request_info) -> std::string {
Upstream::HostDescriptionConstSharedPtr host = request_info.upstreamHost();
if (!host) {
return "";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I slightly prefer "return std::string()" (same for other places in this function)

}

std::function<std::string(const Envoy::AccessLog::RequestInfo&)>
parseUpstreamMetadataField(const std::string& params_str) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment with an example string of the correct format near the top of the function, so I can quickly see visually what it is this function is parsing?

return value->bool_value() ? "true" : "false";

default:
// Unsupported type or null value.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This case will be hard for a user to debug. Since we found something, but it's the wrong type, would it be better to return "unsupported metadata type" or something, or perhaps log something?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think returning an arbitrary string would be bad. I've added a log message.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO debug is a more appropriate log level here, as this could emit at high rate due to external factors.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Dumb story, but yes, that's what I forgot to change it back to before committing.

return request_info.getDownstreamAddress();
};
} else if (StringUtil::startsWith(field_name.c_str(), "UPSTREAM_METADATA")) {
field_extractor_ = parseUpstreamMetadataField(field_name.substr(17));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@zuercher zuercher merged commit a4fa738 into envoyproxy:master Dec 13, 2017
@zuercher zuercher deleted the impl-2148-dynamic-host-headers branch December 15, 2017 17:36
Shikugawa pushed a commit to Shikugawa/envoy that referenced this pull request Mar 28, 2020
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.

4 participants