Skip to content

Commit

Permalink
Server extension: start factoring out common code / functionality.
Browse files Browse the repository at this point in the history
A first step to clean this up. Functional no-op.

Part of envoyproxy#498

Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
  • Loading branch information
oschaaf committed Sep 6, 2020
1 parent 00b6861 commit 1693789
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 141 deletions.
9 changes: 2 additions & 7 deletions ci/docker/default-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,8 @@ static_resources:
domains:
- "*"
http_filters:
- name: envoy.fault
config:
max_active_faults: 100
delay:
header_delay: {}
percentage:
numerator: 100
- name: time-tracking
- name: dynamic-delay
- name: test-server
config:
response_body_size: 10
Expand Down
16 changes: 11 additions & 5 deletions source/server/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ envoy_cc_library(

envoy_cc_library(
name = "configuration_lib",
srcs = ["configuration.cc"],
hdrs = ["configuration.h"],
srcs = [
"configuration.cc",
"http_filter_config_base.cc",
],
hdrs = [
"configuration.h",
"http_filter_config_base.h",
],
repository = "@envoy",
deps = [
":well_known_headers_lib",
"//api/server:response_options_proto_cc_proto",
"@envoy//include/envoy/server:filter_config_interface_with_external_headers",
"@envoy//source/common/common:statusor_lib_with_external_headers",
"@envoy//source/common/protobuf:message_validator_lib_with_external_headers",
"@envoy//source/common/protobuf:utility_lib_with_external_headers",
"@envoy//source/common/singleton:const_singleton_with_external_headers",
Expand All @@ -38,7 +47,6 @@ envoy_cc_library(
repository = "@envoy",
deps = [
":configuration_lib",
":well_known_headers_lib",
"//api/server:response_options_proto_cc_proto",
"@envoy//source/exe:envoy_common_lib_with_external_headers",
],
Expand All @@ -51,7 +59,6 @@ envoy_cc_library(
repository = "@envoy",
deps = [
":configuration_lib",
":well_known_headers_lib",
"//api/server:response_options_proto_cc_proto",
"//source/common:thread_safe_monotonic_time_stopwatch_lib",
"@envoy//source/exe:envoy_common_lib_with_external_headers",
Expand All @@ -66,7 +73,6 @@ envoy_cc_library(
repository = "@envoy",
deps = [
":configuration_lib",
":well_known_headers_lib",
"//api/server:response_options_proto_cc_proto",
"@envoy//source/exe:envoy_common_lib_with_external_headers",
"@envoy//source/extensions/filters/http/fault:fault_filter_lib_with_external_headers",
Expand Down
2 changes: 1 addition & 1 deletion source/server/configuration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ bool mergeJsonConfig(absl::string_view json, nighthawk::server::ResponseOptions&
}

void applyConfigToResponseHeaders(Envoy::Http::ResponseHeaderMap& response_headers,
nighthawk::server::ResponseOptions& response_options) {
const 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());
Expand Down
2 changes: 1 addition & 1 deletion source/server/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ bool mergeJsonConfig(absl::string_view json, nighthawk::server::ResponseOptions&
* @param response_options Configuration specifying how to transform the header map.
*/
void applyConfigToResponseHeaders(Envoy::Http::ResponseHeaderMap& response_headers,
nighthawk::server::ResponseOptions& response_options);
const nighthawk::server::ResponseOptions& response_options);

} // namespace Configuration
} // namespace Server
Expand Down
37 changes: 11 additions & 26 deletions source/server/http_dynamic_delay_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ namespace Server {
HttpDynamicDelayDecoderFilterConfig::HttpDynamicDelayDecoderFilterConfig(
nighthawk::server::ResponseOptions proto_config, Envoy::Runtime::Loader& runtime,
const std::string& stats_prefix, Envoy::Stats::Scope& scope, Envoy::TimeSource& time_source)
: server_config_(std::move(proto_config)), runtime_(runtime),
stats_prefix_(absl::StrCat(stats_prefix, "dynamic-delay.")), scope_(scope),
: FilterConfigurationBase(std::move(proto_config), "dynamic-delay"), runtime_(runtime),
stats_prefix_(absl::StrCat(stats_prefix, fmt::format("{}.", filter_name()))), scope_(scope),
time_source_(time_source) {}

HttpDynamicDelayDecoderFilter::HttpDynamicDelayDecoderFilter(
Expand All @@ -40,32 +40,17 @@ void HttpDynamicDelayDecoderFilter::onDestroy() {
Envoy::Http::FilterHeadersStatus
HttpDynamicDelayDecoderFilter::decodeHeaders(Envoy::Http::RequestHeaderMap& headers,
bool end_stream) {
response_options_ = config_->server_config();
std::string error_message;
if (!computeResponseOptions(headers, error_message)) {
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;
}
const absl::optional<int64_t> delay_ms =
computeDelayMs(response_options_, config_->approximateFilterInstances());
maybeRequestFaultFilterDelay(delay_ms, headers);
return Envoy::Extensions::HttpFilters::Fault::FaultFilter::decodeHeaders(headers, end_stream);
}

bool HttpDynamicDelayDecoderFilter::computeResponseOptions(
const Envoy::Http::RequestHeaderMap& headers, std::string& error_message) {
response_options_ = config_->server_config();
const auto* request_config_header = headers.get(TestServer::HeaderNames::get().TestServerConfig);
if (request_config_header) {
if (!Configuration::mergeJsonConfig(request_config_header->value().getStringView(),
response_options_, error_message)) {
return false;
config_->computeEffectiveConfiguration(headers);
if (end_stream) {
if (config_->maybeSendErrorReply(*decoder_callbacks_)) {
return Envoy::Http::FilterHeadersStatus::StopIteration;
} else {
const absl::optional<int64_t> delay_ms = computeDelayMs(
*config_->getEffectiveConfiguration().value(), config_->approximateFilterInstances());
maybeRequestFaultFilterDelay(delay_ms, headers);
}
}
return true;
return Envoy::Extensions::HttpFilters::Fault::FaultFilter::decodeHeaders(headers, end_stream);
}

absl::optional<int64_t> HttpDynamicDelayDecoderFilter::computeDelayMs(
Expand Down
26 changes: 3 additions & 23 deletions source/server/http_dynamic_delay_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#include "api/server/response_options.pb.h"

#include "server/http_filter_config_base.h"

namespace Nighthawk {
namespace Server {

Expand All @@ -17,7 +19,7 @@ namespace Server {
* Instances of this class will be shared accross instances of HttpDynamicDelayDecoderFilter.
* The methods for getting and manipulating (global) active filter instance counts are thread safe.
*/
class HttpDynamicDelayDecoderFilterConfig {
class HttpDynamicDelayDecoderFilterConfig : public FilterConfigurationBase {

public:
/**
Expand All @@ -35,13 +37,6 @@ class HttpDynamicDelayDecoderFilterConfig {
Envoy::Runtime::Loader& runtime,
const std::string& stats_prefix, Envoy::Stats::Scope& scope,
Envoy::TimeSource& time_source);

/**
* @return const nighthawk::server::ResponseOptions& read-only reference to the proto config
* object.
*/
const nighthawk::server::ResponseOptions& server_config() const { return server_config_; }

/**
* Increments the number of globally active filter instances.
*/
Expand Down Expand Up @@ -79,7 +74,6 @@ class HttpDynamicDelayDecoderFilterConfig {
std::string stats_prefix() { return stats_prefix_; }

private:
const nighthawk::server::ResponseOptions server_config_;
static std::atomic<uint64_t>& instances() {
// We lazy-init the atomic to avoid static initialization / a fiasco.
MUTABLE_CONSTRUCT_ON_FIRST_USE(std::atomic<uint64_t>, 0); // NOLINT
Expand Down Expand Up @@ -114,19 +108,6 @@ class HttpDynamicDelayDecoderFilter : public Envoy::Extensions::HttpFilters::Fau
Envoy::Http::FilterHeadersStatus decodeHeaders(Envoy::Http::RequestHeaderMap&, bool) override;
void setDecoderFilterCallbacks(Envoy::Http::StreamDecoderFilterCallbacks&) override;

/**
* Compute the response options based on the static configuration and optional configuration
* provided via the request headers. After a successfull call the response_options_ field will
* have been modified to reflect request-level configuration.
*
* @param request_headers The request headers set to inspect for configuration.
* @param error_message Set to an error message if the request-level configuration couldn't be
* interpreted.
* @return true iff the configuration was successfully computed.
*/
bool computeResponseOptions(const Envoy::Http::RequestHeaderMap& request_headers,
std::string& error_message);

/**
* Compute the concurrency based linear delay in milliseconds.
*
Expand Down Expand Up @@ -179,7 +160,6 @@ class HttpDynamicDelayDecoderFilter : public Envoy::Extensions::HttpFilters::Fau
private:
const HttpDynamicDelayDecoderFilterConfigSharedPtr config_;
Envoy::Http::StreamDecoderFilterCallbacks* decoder_callbacks_;
nighthawk::server::ResponseOptions response_options_;
bool destroyed_{false};
};

Expand Down
42 changes: 42 additions & 0 deletions source/server/http_filter_config_base.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "server/http_filter_config_base.h"

namespace Nighthawk {
namespace Server {

FilterConfigurationBase::FilterConfigurationBase(nighthawk::server::ResponseOptions proto_config,
absl::string_view filter_name)
: filter_name_(filter_name),
server_config_(std::make_shared<nighthawk::server::ResponseOptions>(std::move(proto_config))),
effective_config_(server_config_) {}

void FilterConfigurationBase::computeEffectiveConfiguration(
const Envoy::Http::RequestHeaderMap& headers) {
const auto* request_config_header = headers.get(TestServer::HeaderNames::get().TestServerConfig);
if (request_config_header) {
nighthawk::server::ResponseOptions response_options = *server_config_;
std::string error_message;
if (Configuration::mergeJsonConfig(request_config_header->value().getStringView(),
response_options, error_message)) {
effective_config_ =
std::make_shared<const nighthawk::server::ResponseOptions>(std::move(response_options));
} else {
effective_config_ = absl::InvalidArgumentError(error_message);
}
}
}

bool FilterConfigurationBase::maybeSendErrorReply(
Envoy::Http::StreamDecoderFilterCallbacks& decoder_callbacks) const {
if (!effective_config_.ok()) {
decoder_callbacks.sendLocalReply(static_cast<Envoy::Http::Code>(500),
fmt::format("{} didn't understand the request: {}",
filter_name_,
effective_config_.status().message()),
nullptr, absl::nullopt, "");
return true;
}
return false;
}

} // namespace Server
} // namespace Nighthawk
80 changes: 80 additions & 0 deletions source/server/http_filter_config_base.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#pragma once

#include <string>

#include "envoy/server/filter_config.h"

#include "external/envoy/source/common/common/statusor.h"

#include "api/server/response_options.pb.h"

#include "server/configuration.h"
#include "server/well_known_headers.h"

#include "absl/status/status.h"

namespace Nighthawk {
namespace Server {

/**
* Shorthand and canonical representation of the effective filter configuration. Either a status
* or a shared pointer to the effective configuration. We use a shared pointer to avoid copying
* in the static configuration flow.
*/
using EffectiveFilterConfiguration =
absl::StatusOr<std::shared_ptr<const nighthawk::server::ResponseOptions>>;

/**
* Provides functionality for parsing and merging request-header based configuration, as well as
* generating a common error response accross all extensions.
*/
class FilterConfigurationBase {
public:
/**
* @brief Construct a new Filter Configuration Base object
*
* @param proto_config the static disk-based response options configuration
* @param filter_name name of the extension that is consuming this. Used during error response
* generation.
*/
FilterConfigurationBase(nighthawk::server::ResponseOptions static_proto_config,
absl::string_view filter_name);

/**
* Copmute the effective configuration, based on considering the static configuration as well as
* any configuration provided via request headers.
*
* @param request_headers Full set of request headers to be inspected for configuration.
*/
void computeEffectiveConfiguration(const Envoy::Http::RequestHeaderMap& request_headers);

/**
* Send an error reply based on status of the effective configuration. For example, when dynamic
* configuration delivered via request headers could not be parsed or was out of spec.
*
* @param decoder_callbacks Decoder used to generate the reply.
* @return true iff an error reply was generated.
*/
bool maybeSendErrorReply(Envoy::Http::StreamDecoderFilterCallbacks& decoder_callbacks) const;

/**
* @brief Get the effective configuration. Depending on state ,this could be one of static
* configuration, dynamic configuration, or an error status.
*
* @return const EffectiveFilterConfiguration The effective configuration, or an error status.
*/
const EffectiveFilterConfiguration getEffectiveConfiguration() const { return effective_config_; }

/**
* @return absl::string_view Name of the filter that constructed this instance.
*/
absl::string_view filter_name() const { return filter_name_; }

private:
const std::string filter_name_;
const std::shared_ptr<nighthawk::server::ResponseOptions> server_config_;
EffectiveFilterConfiguration effective_config_;
};

} // namespace Server
} // namespace Nighthawk
Loading

0 comments on commit 1693789

Please sign in to comment.