-
Notifications
You must be signed in to change notification settings - Fork 91
Test server dynamic delay #390
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
Changes from 6 commits
503e8c6
b44cf2c
159599f
3ec87c0
277c786
a34b8cd
5d6fb7e
d7bfb51
caf8c78
aeddcdd
b1069da
e0f88a0
c40e9ce
6113345
1cd6cd7
8ee78db
3f006f6
3a875cd
3e3c6ef
ac456f3
9be5334
ad5e34c
3c80a0b
8f74535
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| #include "server/common.h" | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "external/envoy/source/common/protobuf/message_validator_impl.h" | ||
| #include "external/envoy/source/common/protobuf/utility.h" | ||
|
|
||
| #include "api/server/response_options.pb.validate.h" | ||
|
|
||
| #include "absl/strings/numbers.h" | ||
|
|
||
| namespace Nighthawk { | ||
| namespace Server { | ||
|
|
||
| bool Utility::mergeJsonConfig(absl::string_view json, nighthawk::server::ResponseOptions& config, | ||
| absl::optional<std::string>& error_message) { | ||
| error_message = absl::nullopt; | ||
| try { | ||
| nighthawk::server::ResponseOptions json_config; | ||
| auto& validation_visitor = Envoy::ProtobufMessage::getStrictValidationVisitor(); | ||
| Envoy::MessageUtil::loadFromJson(std::string(json), json_config, validation_visitor); | ||
| config.MergeFrom(json_config); | ||
| Envoy::MessageUtil::validate(config, validation_visitor); | ||
| } catch (const Envoy::EnvoyException& exception) { | ||
| error_message.emplace(fmt::format("Error merging json config: {}", exception.what())); | ||
| } | ||
| return error_message == absl::nullopt; | ||
| } | ||
|
|
||
| void Utility::applyConfigToResponseHeaders(Envoy::Http::ResponseHeaderMap& response_headers, | ||
| nighthawk::server::ResponseOptions& response_options) { | ||
| for (const auto& header_value_option : response_options.response_headers()) { | ||
| const auto& header = header_value_option.header(); | ||
| auto lower_case_key = Envoy::Http::LowerCaseString(header.key()); | ||
| if (!header_value_option.append().value()) { | ||
| response_headers.remove(lower_case_key); | ||
| } | ||
| response_headers.addCopy(lower_case_key, header.value()); | ||
| } | ||
| } | ||
|
|
||
| } // namespace Server | ||
| } // namespace Nighthawk |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||
| #pragma once | ||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add unit tests for this library?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are pre-existing unit tests for this. For example, see
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (for the future) Looks like the changes in this library may have been just indirectly related to the work in this PR. If reasonable, can we try to split such changes into their own PR in the future to minimize the size of PRs and the review complexity?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you're right, sorry about that. Will extract these mechanical refactors out into a separate PR next time. |
||||
|
|
||||
| #include <string> | ||||
|
|
||||
| #include "envoy/http/header_map.h" | ||||
|
|
||||
| #include "external/envoy/source/common/singleton/const_singleton.h" | ||||
|
|
||||
| #include "api/server/response_options.pb.h" | ||||
|
|
||||
| namespace Nighthawk { | ||||
| namespace Server { | ||||
|
|
||||
| namespace TestServer { | ||||
|
|
||||
| class HeaderNameValues { | ||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per the comment below (ideally, please read that one first) - are we moving this definition here just because we don't have a better place for it and this file happens to have a generic enough name? And a similar question - does this have to be a class? Alternative approach - this currently only seems to be used in a single location - how about we just inline the literal there, is it worth having a this defined as a constant? If yes, can we define a new header specifically designed for this? And if we are doing that - is attaching these onto a class an idiom used in Envoy, or can we can try to figure out an approach that doesn't require a class? Even an ordinary function returning static constexpr might be preferred. inline absl::string_view MyString() {
static constexpr char kHello[] = "Hello";
return kHello;
}
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like for above, see 9be5334. However, I kept the Envoy construct, because this is shared/reused across the two extensions, and having the |
||||
| public: | ||||
| const Envoy::Http::LowerCaseString TestServerConfig{"x-nighthawk-test-server-config"}; | ||||
| }; | ||||
|
|
||||
| using HeaderNames = Envoy::ConstSingleton<HeaderNameValues>; | ||||
|
|
||||
| } // namespace TestServer | ||||
|
|
||||
| class Utility { | ||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am wondering if we can improve the naming and structure of this new library. Both "common" and "Utility" are very generic names that a) don't tell the reader much and b) attract pretty much any helper / utility function. After all everything is an utility. Secondly it doesn't seem necessary for this to be a class at all, since the two methods on it don't really need any state. How would you feel about changing these into regular functions and renaming the library into something more specific. E.g. response_options.h Feel free to choose any other name that is specific enough not to attract unrelated functions.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 9be5334, let me know if that looks better. |
||||
| public: | ||||
| /** | ||||
| * Merges a json string containing configuration into a ResponseOptions instance. | ||||
| * | ||||
| * @param json Json-formatted seralization of ResponseOptions to merge into the configuration. | ||||
| * @param config The target that the json string should be merged into. | ||||
| * @param error_message Will contain an error message iff an error occurred. | ||||
|
oschaaf marked this conversation as resolved.
Outdated
|
||||
| * @return bool false iff an error occurred. | ||||
| */ | ||||
| static bool mergeJsonConfig(absl::string_view json, nighthawk::server::ResponseOptions& config, | ||||
| absl::optional<std::string>& error_message); | ||||
|
|
||||
| /** | ||||
| * Applies ResponseOptions onto a HeaderMap containing response headers. | ||||
| * | ||||
| * @param response_headers Response headers to transform to reflect the passed in response | ||||
| * options. | ||||
| * @param response_options Configuration specifying how to transform the header map. | ||||
| */ | ||||
| static void applyConfigToResponseHeaders(Envoy::Http::ResponseHeaderMap& response_headers, | ||||
| nighthawk::server::ResponseOptions& response_options); | ||||
| }; | ||||
|
|
||||
| } // namespace Server | ||||
| } // namespace Nighthawk | ||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| #include "server/http_dynamic_delay_filter.h" | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "envoy/server/filter_config.h" | ||
|
|
||
| #include "server/common.h" | ||
|
|
||
| #include "absl/strings/str_cat.h" | ||
|
|
||
| namespace Nighthawk { | ||
| namespace Server { | ||
|
|
||
| std::atomic<uint64_t> HttpDynamicDelayDecoderFilterConfig::instances_(0); | ||
|
oschaaf marked this conversation as resolved.
Outdated
|
||
|
|
||
| HttpDynamicDelayDecoderFilterConfig::HttpDynamicDelayDecoderFilterConfig( | ||
| nighthawk::server::ResponseOptions proto_config) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| : server_config_(std::move(proto_config)) {} | ||
|
|
||
| HttpDynamicDelayDecoderFilter::HttpDynamicDelayDecoderFilter( | ||
| HttpDynamicDelayDecoderFilterConfigSharedPtr config) | ||
| : config_(std::move(config)) { | ||
| config_->incrementInstanceCount(); | ||
| } | ||
|
|
||
| void HttpDynamicDelayDecoderFilter::onDestroy() { config_->decrementInstanceCount(); } | ||
|
|
||
| Envoy::Http::FilterHeadersStatus | ||
| HttpDynamicDelayDecoderFilter::decodeHeaders(Envoy::Http::RequestHeaderMap& headers, bool) { | ||
| base_config_ = config_->server_config(); | ||
| const auto* request_config_header = headers.get(TestServer::HeaderNames::get().TestServerConfig); | ||
| if (request_config_header) { | ||
| Utility::mergeJsonConfig(request_config_header->value().getStringView(), base_config_, | ||
| error_message_); | ||
| } | ||
| if (error_message_.has_value()) { | ||
| decoder_callbacks_->sendLocalReply( | ||
| static_cast<Envoy::Http::Code>(500), | ||
| fmt::format("dynamic-delay didn't understand the request: {}", *error_message_), nullptr, | ||
| absl::nullopt, ""); | ||
| return Envoy::Http::FilterHeadersStatus::StopIteration; | ||
| } | ||
| absl::optional<int64_t> delay; | ||
|
oschaaf marked this conversation as resolved.
Outdated
|
||
| if (base_config_.has_static_delay()) { | ||
| delay = Envoy::Protobuf::util::TimeUtil::DurationToMilliseconds(base_config_.static_delay()); | ||
| } else if (base_config_.has_concurrency_based_delay()) { | ||
| auto& concurrency = base_config_.concurrency_based_delay(); | ||
|
oschaaf marked this conversation as resolved.
Outdated
|
||
| const uint64_t current_value = config_->approximateInstances(); | ||
| delay = Envoy::Protobuf::util::TimeUtil::DurationToMilliseconds( | ||
| concurrency.minimal_delay() + (current_value * concurrency.concurrency_delay_factor())); | ||
| std::cerr << current_value << ":" << delay.value() << std::endl; | ||
| } | ||
| if (delay.has_value() && delay > 0) { | ||
| // Emit header to communicate the delay we desire to the fault filter extension. | ||
| const Envoy::Http::LowerCaseString key("x-envoy-fault-delay-request"); | ||
| headers.setCopy(key, absl::StrCat(*delay)); | ||
| } | ||
| return Envoy::Http::FilterHeadersStatus::Continue; | ||
| } | ||
|
|
||
| Envoy::Http::FilterDataStatus HttpDynamicDelayDecoderFilter::decodeData(Envoy::Buffer::Instance&, | ||
| bool) { | ||
| return Envoy::Http::FilterDataStatus::Continue; | ||
| } | ||
|
|
||
| Envoy::Http::FilterTrailersStatus | ||
| HttpDynamicDelayDecoderFilter::decodeTrailers(Envoy::Http::RequestTrailerMap&) { | ||
| return Envoy::Http::FilterTrailersStatus::Continue; | ||
| } | ||
|
|
||
| void HttpDynamicDelayDecoderFilter::setDecoderFilterCallbacks( | ||
| Envoy::Http::StreamDecoderFilterCallbacks& callbacks) { | ||
| decoder_callbacks_ = &callbacks; | ||
| } | ||
|
|
||
| } // namespace Server | ||
| } // namespace Nighthawk | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| #pragma once | ||
|
|
||
| #include <atomic> | ||
| #include <string> | ||
|
|
||
| #include "envoy/server/filter_config.h" | ||
|
|
||
| #include "api/server/response_options.pb.h" | ||
|
|
||
| namespace Nighthawk { | ||
| namespace Server { | ||
|
|
||
| // Basically this is left in as a placeholder for further configuration. | ||
| class HttpDynamicDelayDecoderFilterConfig { | ||
| public: | ||
| HttpDynamicDelayDecoderFilterConfig(nighthawk::server::ResponseOptions proto_config); | ||
| const nighthawk::server::ResponseOptions& server_config() { return server_config_; } | ||
| void incrementInstanceCount() { instances_++; } | ||
| void decrementInstanceCount() { instances_--; } | ||
| uint64_t approximateInstances() const { return instances_; } | ||
|
|
||
| private: | ||
| const nighthawk::server::ResponseOptions server_config_; | ||
| static std::atomic<uint64_t> instances_; | ||
| }; | ||
|
|
||
| using HttpDynamicDelayDecoderFilterConfigSharedPtr = | ||
| std::shared_ptr<HttpDynamicDelayDecoderFilterConfig>; | ||
|
|
||
| class HttpDynamicDelayDecoderFilter : public Envoy::Http::StreamDecoderFilter { | ||
| public: | ||
| HttpDynamicDelayDecoderFilter(HttpDynamicDelayDecoderFilterConfigSharedPtr); | ||
|
|
||
| // Http::StreamFilterBase | ||
| void onDestroy() override; | ||
|
|
||
| // Http::StreamDecoderFilter | ||
| Envoy::Http::FilterHeadersStatus decodeHeaders(Envoy::Http::RequestHeaderMap&, bool) override; | ||
|
oschaaf marked this conversation as resolved.
|
||
| Envoy::Http::FilterDataStatus decodeData(Envoy::Buffer::Instance&, bool) override; | ||
| Envoy::Http::FilterTrailersStatus decodeTrailers(Envoy::Http::RequestTrailerMap&) override; | ||
| void setDecoderFilterCallbacks(Envoy::Http::StreamDecoderFilterCallbacks&) override; | ||
|
|
||
| private: | ||
| const HttpDynamicDelayDecoderFilterConfigSharedPtr config_; | ||
| Envoy::Http::StreamDecoderFilterCallbacks* decoder_callbacks_; | ||
| nighthawk::server::ResponseOptions base_config_; | ||
| absl::optional<std::string> error_message_; | ||
| }; | ||
|
|
||
| } // namespace Server | ||
| } // namespace Nighthawk | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| #include <string> | ||
|
|
||
| #include "envoy/registry/registry.h" | ||
|
|
||
| #include "external/envoy/source/common/protobuf/message_validator_impl.h" | ||
|
|
||
| #include "api/server/response_options.pb.h" | ||
| #include "api/server/response_options.pb.validate.h" | ||
|
|
||
| #include "server/http_dynamic_delay_filter.h" | ||
|
|
||
| namespace Nighthawk { | ||
| namespace Server { | ||
| namespace Configuration { | ||
|
|
||
| class HttpDynamicDelayDecoderFilterConfig | ||
|
oschaaf marked this conversation as resolved.
Outdated
|
||
| : public Envoy::Server::Configuration::NamedHttpFilterConfigFactory { | ||
| public: | ||
| Envoy::Http::FilterFactoryCb | ||
| createFilterFactoryFromProto(const Envoy::Protobuf::Message& proto_config, const std::string&, | ||
| Envoy::Server::Configuration::FactoryContext& context) override { | ||
|
|
||
| auto& validation_visitor = Envoy::ProtobufMessage::getStrictValidationVisitor(); | ||
| return createFilter( | ||
| Envoy::MessageUtil::downcastAndValidate<const nighthawk::server::ResponseOptions&>( | ||
| proto_config, validation_visitor), | ||
| context); | ||
| } | ||
|
|
||
| Envoy::ProtobufTypes::MessagePtr createEmptyConfigProto() override { | ||
| return Envoy::ProtobufTypes::MessagePtr{new nighthawk::server::ResponseOptions()}; | ||
| } | ||
|
|
||
| std::string name() const override { return "dynamic-delay"; } | ||
|
|
||
| private: | ||
| Envoy::Http::FilterFactoryCb createFilter(const nighthawk::server::ResponseOptions& proto_config, | ||
| Envoy::Server::Configuration::FactoryContext&) { | ||
| Nighthawk::Server::HttpDynamicDelayDecoderFilterConfigSharedPtr config = | ||
| std::make_shared<Nighthawk::Server::HttpDynamicDelayDecoderFilterConfig>( | ||
| Nighthawk::Server::HttpDynamicDelayDecoderFilterConfig(proto_config)); | ||
|
|
||
| return [config](Envoy::Http::FilterChainFactoryCallbacks& callbacks) -> void { | ||
| auto* filter = new Nighthawk::Server::HttpDynamicDelayDecoderFilter(config); | ||
| callbacks.addStreamDecoderFilter(Envoy::Http::StreamDecoderFilterSharedPtr{filter}); | ||
| }; | ||
| } | ||
| }; | ||
|
|
||
| static Envoy::Registry::RegisterFactory<HttpDynamicDelayDecoderFilterConfig, | ||
| Envoy::Server::Configuration::NamedHttpFilterConfigFactory> | ||
| register_; | ||
| } // namespace Configuration | ||
| } // namespace Server | ||
| } // namespace Nighthawk | ||
Uh oh!
There was an error while loading. Please reload this page.