diff --git a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/BUILD b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/BUILD index 98107dafb584..2e88b619e524 100644 --- a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/BUILD +++ b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/BUILD @@ -4,5 +4,14 @@ licenses(["notice"]) # Apache 2 api_proto_library( name = "dubbo_proxy", - srcs = ["dubbo_proxy.proto"], + srcs = [ + "dubbo_proxy.proto", + "route.proto", + ], + deps = [ + "//envoy/api/v2/core:base", + "//envoy/api/v2/route", + "//envoy/type:range", + "//envoy/type/matcher:string", + ], ) diff --git a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto index fd7a02009084..e38e50a50a3e 100644 --- a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto +++ b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto @@ -1,27 +1,54 @@ syntax = "proto3"; -package envoy.extensions.filters.network.dubbo_proxy.v2alpha1; -option java_package = "io.envoyproxy.envoy.extensions.filters.network.dubbo_proxy.v2alpha1"; +package envoy.config.filter.network.dubbo_proxy.v2alpha1; +option java_package = "io.envoyproxy.envoy.config.filter.network.dubbo_proxy.v2alpha1"; option go_package = "v2"; +import "envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto"; + +import "google/protobuf/any.proto"; + import "validate/validate.proto"; +import "gogoproto/gogo.proto"; // [#protodoc-title: Dubbo Proxy] // Dubbo Proxy filter configuration. - message DubboProxy { // The human readable prefix to use when emitting statistics. string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; // Configure the protocol used. - enum ProtocolType { - Dubbo = 0; // the default protocol. - } ProtocolType protocol_type = 2 [(validate.rules).enum.defined_only = true]; // Configure the serialization protocol used. - enum SerializationType { - Hessian2 = 0; // the default serialization protocol. - } SerializationType serialization_type = 3 [(validate.rules).enum.defined_only = true]; + + // The route table for the connection manager is static and is specified in this property. + repeated RouteConfiguration route_config = 4; + + // A list of individual Dubbo filters that make up the filter chain for requests made to the + // Dubbo proxy. Order matters as the filters are processed sequentially. For backwards + // compatibility, if no dubbo_filters are specified, a default Dubbo router filter + // (`envoy.filters.dubbo.router`) is used. + repeated DubboFilter dubbo_filters = 5; } + +enum ProtocolType { + Dubbo = 0; // the default protocol. +} + +enum SerializationType { + Hessian2 = 0; // the default serialization protocol. +} + +// DubboFilter configures a Dubbo filter. +// [#comment:next free field: 3] +message DubboFilter { + // The name of the filter to instantiate. The name must match a supported + // filter. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Filter specific configuration which depends on the filter being + // instantiated. See the supported filters for further documentation. + google.protobuf.Any config = 2; +} \ No newline at end of file diff --git a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto new file mode 100644 index 000000000000..26ee220953e9 --- /dev/null +++ b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto @@ -0,0 +1,103 @@ +syntax = "proto3"; + +package envoy.config.filter.network.dubbo_proxy.v2alpha1; +option java_package = "io.envoyproxy.envoy.config.filter.network.dubbo_proxy.v2alpha1"; +option go_package = "v2"; + +import "envoy/api/v2/route/route.proto"; +import "envoy/type/matcher/string.proto"; +import "envoy/type/range.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; +import "gogoproto/gogo.proto"; + +// [#protodoc-title: Dubbo route configuration] + +message RouteConfiguration { + // The name of the route configuration. Reserved for future use in asynchronous route discovery. + string name = 1; + + // The interface name of the service. + string interface = 2; + + // Which group does the interface belong to. + string group = 3; + + // The version number of the interface. + string version = 4; + + // The list of routes that will be matched, in order, against incoming requests. The first route + // that matches will be used. + repeated Route routes = 5 [(gogoproto.nullable) = false]; +} + +message Route { + // Route matching prarameters. + RouteMatch match = 1 [(validate.rules).message.required = true, (gogoproto.nullable) = false]; + + // Route request to some upstream cluster. + RouteAction route = 2 [(validate.rules).message.required = true, (gogoproto.nullable) = false]; +} + +message MethodMatch { + // The name of the method. + envoy.type.matcher.StringMatcher name = 1; + + // The parameter matching type. + message ParameterMatchSpecifier { + oneof parameter_match_specifier { + // If specified, header match will be performed based on the value of the header. + string exact_match = 3; + + // If specified, header match will be performed based on range. + // The rule will match if the request header value is within this range. + // The entire request header value must represent an integer in base 10 notation: consisting + // of an optional plus or minus sign followed by a sequence of digits. The rule will not match + // if the header value does not represent an integer. Match will fail for empty values, + // floating point numbers or if only a subsequence of the header value is an integer. + // + // Examples: + // + // * For range [-10,0), route will match for header value -1, but not for 0, + // "somestring", 10.9, + // "-1somestring" + envoy.type.Int64Range range_match = 4; + } + } + + // Method parameter definition. + // The key is the parameter index, starting from 0. + // The value is the parameter matching type. + map params_match = 2; +} + +message RouteMatch { + // Method level routing matching. + MethodMatch method = 1; + + // Specifies a set of headers that the route should match on. The router will check the request’s + // headers against all the specified headers in the route config. A match will happen if all the + // headers in the route are present in the request with the same values (or based on presence if + // the value field is not in the config). + repeated envoy.api.v2.route.HeaderMatcher headers = 2; +} + +// [#comment:next free field: 2] +message RouteAction { + oneof cluster_specifier { + option (validate.required) = true; + + // Indicates the upstream cluster to which the request should be routed. + string cluster = 1; + + // Multiple upstream clusters can be specified for a given route. The + // request is routed to one of the upstream clusters based on weights + // assigned to each cluster. + // + // .. note:: + // Currently ClusterWeight only supports the name and weight fields. + envoy.api.v2.route.WeightedCluster weighted_clusters = 2; + } +} diff --git a/source/extensions/filters/network/dubbo_proxy/BUILD b/source/extensions/filters/network/dubbo_proxy/BUILD index 5905adc08702..b95792467bef 100644 --- a/source/extensions/filters/network/dubbo_proxy/BUILD +++ b/source/extensions/filters/network/dubbo_proxy/BUILD @@ -100,6 +100,7 @@ envoy_cc_library( hdrs = ["filter.h"], deps = [ ":decoder_lib", + ":stats_lib", "//include/envoy/network:connection_interface", "//include/envoy/network:filter_interface", "//include/envoy/stats:stats_interface", @@ -151,3 +152,12 @@ envoy_cc_library( "//source/common/buffer:buffer_lib", ], ) + +envoy_cc_library( + name = "stats_lib", + hdrs = ["stats.h"], + deps = [ + "//include/envoy/stats:stats_interface", + "//include/envoy/stats:stats_macros", + ], +) diff --git a/source/extensions/filters/network/dubbo_proxy/config.cc b/source/extensions/filters/network/dubbo_proxy/config.cc index 01bf22ed8923..9634ff23bb28 100644 --- a/source/extensions/filters/network/dubbo_proxy/config.cc +++ b/source/extensions/filters/network/dubbo_proxy/config.cc @@ -10,7 +10,7 @@ namespace NetworkFilters { namespace DubboProxy { Network::FilterFactoryCb DubboProxyFilterConfigFactory::createFilterFactoryFromProtoTyped( - const envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy& proto_config, + const envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy& proto_config, Server::Configuration::FactoryContext& context) { ASSERT(!proto_config.stat_prefix().empty()); diff --git a/source/extensions/filters/network/dubbo_proxy/config.h b/source/extensions/filters/network/dubbo_proxy/config.h index 568800b3e672..0963ce001949 100644 --- a/source/extensions/filters/network/dubbo_proxy/config.h +++ b/source/extensions/filters/network/dubbo_proxy/config.h @@ -16,13 +16,13 @@ namespace DubboProxy { */ class DubboProxyFilterConfigFactory : public Common::FactoryBase< - envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy> { + envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy> { public: DubboProxyFilterConfigFactory() : FactoryBase(NetworkFilterNames::get().DubboProxy) {} private: Network::FilterFactoryCb createFilterFactoryFromProtoTyped( - const envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy& proto_config, + const envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy& proto_config, Server::Configuration::FactoryContext& context) override; }; diff --git a/source/extensions/filters/network/dubbo_proxy/filter.cc b/source/extensions/filters/network/dubbo_proxy/filter.cc index 2be701555163..823b389143ef 100644 --- a/source/extensions/filters/network/dubbo_proxy/filter.cc +++ b/source/extensions/filters/network/dubbo_proxy/filter.cc @@ -15,16 +15,14 @@ namespace DubboProxy { namespace { -using ConfigProtocolType = - envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy_ProtocolType; +using ConfigProtocolType = envoy::config::filter::network::dubbo_proxy::v2alpha1::ProtocolType; typedef std::map ProtocolTypeMap; static const ProtocolTypeMap& protocolTypeMap() { - CONSTRUCT_ON_FIRST_USE( - ProtocolTypeMap, { - {ConfigProtocolType::DubboProxy_ProtocolType_Dubbo, ProtocolType::Dubbo}, - }); + CONSTRUCT_ON_FIRST_USE(ProtocolTypeMap, { + {ConfigProtocolType::Dubbo, ProtocolType::Dubbo}, + }); } ProtocolType lookupProtocolType(ConfigProtocolType config_type) { @@ -32,31 +30,29 @@ ProtocolType lookupProtocolType(ConfigProtocolType config_type) { if (iter == protocolTypeMap().end()) { throw EnvoyException(fmt::format( "unknown protocol {}", - envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy_ProtocolType_Name( - config_type))); + envoy::config::filter::network::dubbo_proxy::v2alpha1::ProtocolType_Name(config_type))); } return iter->second; } using ConfigSerializationType = - envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy_SerializationType; + envoy::config::filter::network::dubbo_proxy::v2alpha1::SerializationType; typedef std::map SerializationTypeMap; static const SerializationTypeMap& serializationTypeMap() { CONSTRUCT_ON_FIRST_USE(SerializationTypeMap, { - {ConfigSerializationType::DubboProxy_SerializationType_Hessian2, - SerializationType::Hessian}, + {ConfigSerializationType::Hessian2, SerializationType::Hessian}, }); } SerializationType lookupSerializationType(ConfigSerializationType type) { const auto& iter = serializationTypeMap().find(type); if (iter == serializationTypeMap().end()) { - throw EnvoyException(fmt::format("unknown deserializer {}", - envoy::extensions::filters::network::dubbo_proxy::v2alpha1:: - DubboProxy_SerializationType_Name(type))); + throw EnvoyException(fmt::format( + "unknown deserializer {}", + envoy::config::filter::network::dubbo_proxy::v2alpha1::SerializationType_Name(type))); } return iter->second; @@ -67,7 +63,8 @@ SerializationType lookupSerializationType(ConfigSerializationType type) { Filter::Filter(const std::string& stat_prefix, ConfigProtocolType protocol_type, ConfigSerializationType serialization_type, Stats::Scope& scope, TimeSource& time_source) - : stats_(generateStats(stat_prefix, scope)), protocol_type_(lookupProtocolType(protocol_type)), + : stats_(DubboFilterStats::generateStats(stat_prefix, scope)), + protocol_type_(lookupProtocolType(protocol_type)), serialization_type_(lookupSerializationType(serialization_type)), time_source_(time_source) {} Filter::~Filter() = default; @@ -221,12 +218,6 @@ void Filter::onRpcResult(RpcResultPtr&& res) { } } -DubboFilterStats Filter::generateStats(const std::string& prefix, Stats::Scope& scope) { - return DubboFilterStats{ALL_DUBBO_FILTER_STATS(POOL_COUNTER_PREFIX(scope, prefix), - POOL_GAUGE_PREFIX(scope, prefix), - POOL_HISTOGRAM_PREFIX(scope, prefix))}; -} - DecoderPtr Filter::createDecoder(ProtocolCallbacks& prot_callback) { auto parser = createProtocol(prot_callback); auto serializer = createDeserializer(); diff --git a/source/extensions/filters/network/dubbo_proxy/filter.h b/source/extensions/filters/network/dubbo_proxy/filter.h index 4af8cfb8cc21..c8e24d0a58c4 100644 --- a/source/extensions/filters/network/dubbo_proxy/filter.h +++ b/source/extensions/filters/network/dubbo_proxy/filter.h @@ -15,53 +15,25 @@ #include "extensions/filters/network/dubbo_proxy/decoder.h" #include "extensions/filters/network/dubbo_proxy/deserializer.h" #include "extensions/filters/network/dubbo_proxy/protocol.h" +#include "extensions/filters/network/dubbo_proxy/stats.h" namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace DubboProxy { -/** - * All dubbo filter stats. @see stats_macros.h - */ -// clang-format off -#define ALL_DUBBO_FILTER_STATS(COUNTER, GAUGE, HISTOGRAM) \ - COUNTER(request) \ - COUNTER(request_twoway) \ - COUNTER(request_oneway) \ - COUNTER(request_event) \ - COUNTER(request_invalid_type) \ - COUNTER(request_decoding_error) \ - GAUGE(request_active) \ - HISTOGRAM(request_time_ms) \ - COUNTER(response) \ - COUNTER(response_success) \ - COUNTER(response_error) \ - COUNTER(response_exception) \ - COUNTER(response_decoding_error) \ - COUNTER(cx_destroy_local_with_active_rq) \ - COUNTER(cx_destroy_remote_with_active_rq) \ -// clang-format on - -/** - * Struct definition for all dubbo proxy stats. @see stats_macros.h - */ -struct DubboFilterStats { - ALL_DUBBO_FILTER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) -}; - class Filter : public Network::Filter, public Network::ConnectionCallbacks, public ProtocolCallbacks, public DecoderCallbacks, Logger::Loggable { public: - using ConfigProtocolType = envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy_ProtocolType; - using ConfigSerializationType = envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy_SerializationType; + using ConfigProtocolType = envoy::config::filter::network::dubbo_proxy::v2alpha1::ProtocolType; + using ConfigSerializationType = + envoy::config::filter::network::dubbo_proxy::v2alpha1::SerializationType; Filter(const std::string& stat_prefix, ConfigProtocolType protocol_type, - ConfigSerializationType serialization_type, Stats::Scope& scope, - TimeSource& time_source); + ConfigSerializationType serialization_type, Stats::Scope& scope, TimeSource& time_source); virtual ~Filter(); // Network::ReadFilter @@ -93,8 +65,8 @@ class Filter : public Network::Filter, // ActiveMessage tracks downstream requests for which no response has been received. struct ActiveMessage { ActiveMessage(Filter& parent, int32_t request_id) - : parent_(parent), - request_timer_(new Stats::Timespan(parent_.stats_.request_time_ms_, parent_.time_source_)), + : parent_(parent), request_timer_(new Stats::Timespan(parent_.stats_.request_time_ms_, + parent_.time_source_)), request_id_(request_id) { parent_.stats_.request_active_.inc(); } @@ -110,9 +82,6 @@ class Filter : public Network::Filter, }; typedef std::unique_ptr ActiveMessagePtr; - DubboFilterStats generateStats(const std::string& prefix, - Stats::Scope& scope); - // Downstream request decoder, callbacks, and buffer. DecoderPtr request_decoder_; Buffer::OwnedImpl request_buffer_; diff --git a/source/extensions/filters/network/dubbo_proxy/router/BUILD b/source/extensions/filters/network/dubbo_proxy/router/BUILD index f3c69b76c5c6..674713cb072e 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/BUILD +++ b/source/extensions/filters/network/dubbo_proxy/router/BUILD @@ -16,3 +16,19 @@ envoy_cc_library( "//source/extensions/filters/network/dubbo_proxy:metadata_lib", ], ) + +envoy_cc_library( + name = "router_matcher", + srcs = ["route_matcher.cc"], + hdrs = ["route_matcher.h"], + deps = [ + ":router_interface", + "//include/envoy/router:router_interface", + "//source/common/common:logger_lib", + "//source/common/common:matchers_lib", + "//source/common/http:header_utility_lib", + "//source/common/protobuf:utility_lib", + "//source/extensions/filters/network/dubbo_proxy:metadata_lib", + "@envoy_api//envoy/config/filter/network/dubbo_proxy/v2alpha1:dubbo_proxy_cc", + ], +) diff --git a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc new file mode 100644 index 000000000000..5b08076fe31e --- /dev/null +++ b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc @@ -0,0 +1,217 @@ +#include "extensions/filters/network/dubbo_proxy/router/route_matcher.h" + +#include "envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.pb.h" +#include "envoy/config/filter/network/dubbo_proxy/v2alpha1/route.pb.validate.h" + +#include "common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace DubboProxy { +namespace Router { + +RouteEntryImplBase::RouteEntryImplBase( + const envoy::config::filter::network::dubbo_proxy::v2alpha1::Route& route) + : cluster_name_(route.route().cluster()) { + for (const auto& header_map : route.match().headers()) { + config_headers_.emplace_back(header_map); + } + + if (route.route().cluster_specifier_case() == + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteAction::kWeightedClusters) { + total_cluster_weight_ = 0UL; + for (const auto& cluster : route.route().weighted_clusters().clusters()) { + weighted_clusters_.emplace_back(std::make_shared(*this, cluster)); + total_cluster_weight_ += weighted_clusters_.back()->clusterWeight(); + } + ENVOY_LOG(debug, "dubbo route matcher: weighted_clusters_size {}", weighted_clusters_.size()); + } +} + +const std::string& RouteEntryImplBase::clusterName() const { return cluster_name_; } + +const RouteEntry* RouteEntryImplBase::routeEntry() const { return this; } + +RouteConstSharedPtr RouteEntryImplBase::clusterEntry(uint64_t random_value) const { + if (weighted_clusters_.empty()) { + ENVOY_LOG(debug, "dubbo route matcher: weighted_clusters_size {}", weighted_clusters_.size()); + return shared_from_this(); + } + + return WeightedClusterUtil::pickCluster(weighted_clusters_, total_cluster_weight_, random_value, + false); +} + +bool RouteEntryImplBase::headersMatch(const Http::HeaderMap& headers) const { + ENVOY_LOG(debug, "dubbo route matcher: headers size {}, metadata headers size {}", + config_headers_.size(), headers.size()); + return Http::HeaderUtility::matchHeaders(headers, config_headers_); +} + +RouteEntryImplBase::WeightedClusterEntry::WeightedClusterEntry(const RouteEntryImplBase& parent, + const WeightedCluster& cluster) + : parent_(parent), cluster_name_(cluster.name()), + cluster_weight_(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, weight)) {} + +ParameterRouteEntryImpl::ParameterRouteEntryImpl( + const envoy::config::filter::network::dubbo_proxy::v2alpha1::Route& route) + : RouteEntryImplBase(route) { + for (auto& config : route.match().method().params_match()) { + parameter_data_list_.emplace_back(config.first, config.second); + } +} + +ParameterRouteEntryImpl::~ParameterRouteEntryImpl() {} + +bool ParameterRouteEntryImpl::matchParameter(const std::string& request_data, + const ParameterData& config_data) const { + switch (config_data.match_type_) { + case Http::HeaderUtility::HeaderMatchType::Value: + return config_data.value_.empty() || request_data == config_data.value_; + case Http::HeaderUtility::HeaderMatchType::Range: { + int64_t value = 0; + return StringUtil::atol(request_data.c_str(), value, 10) && + value >= config_data.range_.start() && value < config_data.range_.end(); + } + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } +} + +RouteConstSharedPtr ParameterRouteEntryImpl::matches(const MessageMetadata& metadata, + uint64_t random_value) const { + if (!metadata.hasParameters()) { + return nullptr; + } + + ENVOY_LOG(debug, "dubbo route matcher: parameter name match"); + for (auto& config_data : parameter_data_list_) { + const std::string& data = metadata.getParameterValue(config_data.index_); + if (data.empty()) { + ENVOY_LOG(debug, + "dubbo route matcher: parameter matching failed, there are no parameters in the " + "user request, index '{}'", + config_data.index_); + return nullptr; + } + + if (!matchParameter(data, config_data)) { + ENVOY_LOG(debug, "dubbo route matcher: parameter matching failed, index '{}', value '{}'", + config_data.index_, data); + return nullptr; + } + } + + return clusterEntry(random_value); +} + +ParameterRouteEntryImpl::ParameterData::ParameterData(uint32_t index, + const ParameterMatchSpecifier& config) { + index_ = index; + switch (config.parameter_match_specifier_case()) { + case ParameterMatchSpecifier::kExactMatch: + match_type_ = Http::HeaderUtility::HeaderMatchType::Value; + value_ = config.exact_match(); + break; + case ParameterMatchSpecifier::kRangeMatch: + match_type_ = Http::HeaderUtility::HeaderMatchType::Range; + range_.set_start(config.range_match().start()); + range_.set_end(config.range_match().end()); + break; + default: + match_type_ = Http::HeaderUtility::HeaderMatchType::Value; + break; + } +} + +MethodRouteEntryImpl::MethodRouteEntryImpl( + const envoy::config::filter::network::dubbo_proxy::v2alpha1::Route& route) + : RouteEntryImplBase(route), method_name_(route.match().method().name()) { + if (route.match().method().params_match_size() != 0) { + parameter_route_ = std::make_shared(route); + } +} + +MethodRouteEntryImpl::~MethodRouteEntryImpl() {} + +RouteConstSharedPtr MethodRouteEntryImpl::matches(const MessageMetadata& metadata, + uint64_t random_value) const { + if (metadata.hasHeaders() && !RouteEntryImplBase::headersMatch(metadata.headers())) { + ENVOY_LOG(error, "dubbo route matcher: headers not match"); + return nullptr; + } + + if (!metadata.method_name().has_value()) { + ENVOY_LOG(error, "dubbo route matcher: there is no method name in the metadata"); + return nullptr; + } + + if (!method_name_.match(metadata.method_name().value())) { + ENVOY_LOG(debug, "dubbo route matcher: method matching failed, input method '{}'", + metadata.method_name().value()); + return nullptr; + } + + if (parameter_route_) { + ENVOY_LOG(debug, "dubbo route matcher: parameter matching is required"); + return parameter_route_->matches(metadata, random_value); + } + + return clusterEntry(random_value); +} + +RouteMatcher::RouteMatcher(const RouteConfig& config) + : service_name_(config.interface()), group_(config.group()), version_(config.version()) { + using envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteMatch; + + for (const auto& route : config.routes()) { + routes_.emplace_back(std::make_shared(route)); + } + ENVOY_LOG(debug, "dubbo route matcher: routes list size {}", routes_.size()); +} + +RouteConstSharedPtr RouteMatcher::route(const MessageMetadata& metadata, + uint64_t random_value) const { + if (service_name_ == metadata.service_name() && + (group_.value().empty() || + (metadata.service_group().has_value() && metadata.service_group().value() == group_)) && + (version_.value().empty() || (metadata.service_version().has_value() && + metadata.service_version().value() == version_))) { + for (const auto& route : routes_) { + RouteConstSharedPtr route_entry = route->matches(metadata, random_value); + if (nullptr != route_entry) { + return route_entry; + } + } + } else { + ENVOY_LOG(debug, "dubbo route matcher: interface matching failed"); + } + + return nullptr; +} + +MultiRouteMatcher::MultiRouteMatcher(const RouteConfigList& route_config_list) { + for (const auto& route_config : route_config_list) { + route_matcher_list_.emplace_back(std::make_unique(route_config)); + } + ENVOY_LOG(debug, "route matcher list size {}", route_matcher_list_.size()); +} + +RouteConstSharedPtr MultiRouteMatcher::route(const MessageMetadata& metadata, + uint64_t random_value) const { + for (const auto& route_matcher : route_matcher_list_) { + auto route = route_matcher->route(metadata, random_value); + if (nullptr != route) { + return route; + } + } + + return nullptr; +} + +} // namespace Router +} // namespace DubboProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h new file mode 100644 index 000000000000..9eefcbe241bc --- /dev/null +++ b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include + +#include "envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.pb.h" +#include "envoy/config/filter/network/dubbo_proxy/v2alpha1/route.pb.h" + +#include "common/common/logger.h" +#include "common/common/matchers.h" +#include "common/http/header_utility.h" +#include "common/protobuf/protobuf.h" + +#include "extensions/filters/network/dubbo_proxy/metadata.h" +#include "extensions/filters/network/dubbo_proxy/router/router.h" + +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace DubboProxy { +namespace Router { + +class RouteEntryImplBase : public RouteEntry, + public Route, + public std::enable_shared_from_this, + public Logger::Loggable { +public: + RouteEntryImplBase(const envoy::config::filter::network::dubbo_proxy::v2alpha1::Route& route); + virtual ~RouteEntryImplBase() = default; + + // Router::RouteEntry + const std::string& clusterName() const override; + const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() const override { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + + // Router::Route + const RouteEntry* routeEntry() const override; + + virtual RouteConstSharedPtr matches(const MessageMetadata& metadata, + uint64_t random_value) const PURE; + +protected: + RouteConstSharedPtr clusterEntry(uint64_t random_value) const; + bool headersMatch(const Http::HeaderMap& headers) const; + +private: + class WeightedClusterEntry : public RouteEntry, public Route { + public: + using WeightedCluster = envoy::api::v2::route::WeightedCluster_ClusterWeight; + WeightedClusterEntry(const RouteEntryImplBase& parent, const WeightedCluster& cluster); + + uint64_t clusterWeight() const { return cluster_weight_; } + + // Router::RouteEntry + const std::string& clusterName() const override { return cluster_name_; } + const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() const override { + return metadata_match_criteria_ ? metadata_match_criteria_.get() + : parent_.metadataMatchCriteria(); + } + + // Router::Route + const RouteEntry* routeEntry() const override { return this; } + + private: + const RouteEntryImplBase& parent_; + const std::string cluster_name_; + const uint64_t cluster_weight_; + Envoy::Router::MetadataMatchCriteriaConstPtr metadata_match_criteria_; + }; + + typedef std::shared_ptr WeightedClusterEntrySharedPtr; + + uint64_t total_cluster_weight_; + const std::string cluster_name_; + std::vector config_headers_; + std::vector weighted_clusters_; + + // TODO(leilei.gll) Implement it. + Envoy::Router::MetadataMatchCriteriaConstPtr metadata_match_criteria_; +}; + +typedef std::shared_ptr RouteEntryImplBaseConstSharedPtr; + +class ParameterRouteEntryImpl : public RouteEntryImplBase { +public: + ParameterRouteEntryImpl( + const envoy::config::filter::network::dubbo_proxy::v2alpha1::Route& route); + ~ParameterRouteEntryImpl() override; + + struct ParameterData { + using ParameterMatchSpecifier = + envoy::config::filter::network::dubbo_proxy::v2alpha1::MethodMatch_ParameterMatchSpecifier; + ParameterData(uint32_t index, const ParameterMatchSpecifier& config); + + Http::HeaderUtility::HeaderMatchType match_type_; + std::string value_; + envoy::type::Int64Range range_; + uint32_t index_; + }; + + // RoutEntryImplBase + RouteConstSharedPtr matches(const MessageMetadata& metadata, + uint64_t random_value) const override; + +private: + bool matchParameter(const std::string& request_data, const ParameterData& config_data) const; + + std::vector parameter_data_list_; +}; + +class MethodRouteEntryImpl : public RouteEntryImplBase { +public: + MethodRouteEntryImpl(const envoy::config::filter::network::dubbo_proxy::v2alpha1::Route& route); + ~MethodRouteEntryImpl() override; + + // RoutEntryImplBase + RouteConstSharedPtr matches(const MessageMetadata& metadata, + uint64_t random_value) const override; + +private: + const Matchers::StringMatcher method_name_; + std::shared_ptr parameter_route_; +}; + +class RouteMatcher : public Logger::Loggable { +public: + using RouteConfig = envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration; + RouteMatcher(const RouteConfig& config); + + RouteConstSharedPtr route(const MessageMetadata& metadata, uint64_t random_value) const; + +private: + std::vector routes_; + const std::string service_name_; + const absl::optional group_; + const absl::optional version_; +}; + +typedef std::shared_ptr RouteMatcherConstSharedPtr; +typedef std::unique_ptr RouteMatcherPtr; + +class MultiRouteMatcher : public Logger::Loggable { +public: + using RouteConfigList = Envoy::Protobuf::RepeatedPtrField< + ::envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration>; + MultiRouteMatcher(const RouteConfigList& route_config_list); + + RouteConstSharedPtr route(const MessageMetadata& metadata, uint64_t random_value) const; + +private: + std::vector route_matcher_list_; +}; + +} // namespace Router +} // namespace DubboProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/dubbo_proxy/stats.h b/source/extensions/filters/network/dubbo_proxy/stats.h new file mode 100644 index 000000000000..b488401854d7 --- /dev/null +++ b/source/extensions/filters/network/dubbo_proxy/stats.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace DubboProxy { + +/** + * All dubbo filter stats. @see stats_macros.h + */ +// clang-format off +#define ALL_DUBBO_FILTER_STATS(COUNTER, GAUGE, HISTOGRAM) \ + COUNTER(request) \ + COUNTER(request_twoway) \ + COUNTER(request_oneway) \ + COUNTER(request_event) \ + COUNTER(request_invalid_type) \ + COUNTER(request_decoding_error) \ + GAUGE(request_active) \ + HISTOGRAM(request_time_ms) \ + COUNTER(response) \ + COUNTER(response_success) \ + COUNTER(response_error) \ + COUNTER(response_exception) \ + COUNTER(response_decoding_error) \ + COUNTER(cx_destroy_local_with_active_rq) \ + COUNTER(cx_destroy_remote_with_active_rq) \ + COUNTER(downstream_flow_control_paused_reading_total) \ + COUNTER(downstream_flow_control_resumed_reading_total) \ +// clang-format on + +/** + * Struct definition for all dubbo proxy stats. @see stats_macros.h + */ +struct DubboFilterStats { + ALL_DUBBO_FILTER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) + + static DubboFilterStats generateStats(const std::string& prefix, Stats::Scope& scope) { + return DubboFilterStats{ALL_DUBBO_FILTER_STATS(POOL_COUNTER_PREFIX(scope, prefix), + POOL_GAUGE_PREFIX(scope, prefix), + POOL_HISTOGRAM_PREFIX(scope, prefix))}; + } +}; + +} // namespace DubboProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/dubbo_proxy/BUILD b/test/extensions/filters/network/dubbo_proxy/BUILD index e9aef77ed58a..0f77b98b12b9 100644 --- a/test/extensions/filters/network/dubbo_proxy/BUILD +++ b/test/extensions/filters/network/dubbo_proxy/BUILD @@ -106,3 +106,15 @@ envoy_extension_cc_test( "//source/extensions/filters/network/dubbo_proxy:metadata_lib", ], ) + +envoy_extension_cc_test( + name = "route_matcher_test", + srcs = ["route_matcher_test.cc"], + extension_name = "envoy.filters.network.dubbo_proxy", + deps = [ + "//source/extensions/filters/network/dubbo_proxy:metadata_lib", + "//source/extensions/filters/network/dubbo_proxy/router:router_matcher", + "//test/mocks/server:server_mocks", + "@envoy_api//envoy/config/filter/network/dubbo_proxy/v2alpha1:dubbo_proxy_cc", + ], +) diff --git a/test/extensions/filters/network/dubbo_proxy/config_test.cc b/test/extensions/filters/network/dubbo_proxy/config_test.cc index a245e29ca74a..11e5e3f5ef01 100644 --- a/test/extensions/filters/network/dubbo_proxy/config_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/config_test.cc @@ -16,14 +16,13 @@ namespace DubboProxy { TEST(DubboFilterConfigTest, ValidateFail) { NiceMock context; - EXPECT_THROW( - DubboProxyFilterConfigFactory().createFilterFactoryFromProto( - envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy(), context), - ProtoValidationException); + EXPECT_THROW(DubboProxyFilterConfigFactory().createFilterFactoryFromProto( + envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy(), context), + ProtoValidationException); } TEST(DubboFilterConfigTest, ValidProtoConfiguration) { - envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy config{}; + envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy config{}; config.set_stat_prefix("my_stat_prefix"); @@ -38,8 +37,8 @@ TEST(DubboFilterConfigTest, ValidProtoConfiguration) { TEST(DubboFilterConfigTest, DubboProxyWithEmptyProto) { NiceMock context; DubboProxyFilterConfigFactory factory; - envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy config = - *dynamic_cast( + envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy config = + *dynamic_cast( factory.createEmptyConfigProto().get()); config.set_stat_prefix("my_stat_prefix"); diff --git a/test/extensions/filters/network/dubbo_proxy/filter_test.cc b/test/extensions/filters/network/dubbo_proxy/filter_test.cc index 8503c8533c1b..024224ce6335 100644 --- a/test/extensions/filters/network/dubbo_proxy/filter_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/filter_test.cc @@ -21,7 +21,9 @@ namespace Extensions { namespace NetworkFilters { namespace DubboProxy { -using ConfigDubboProxy = envoy::extensions::filters::network::dubbo_proxy::v2alpha1::DubboProxy; +using ConfigProtocolType = envoy::config::filter::network::dubbo_proxy::v2alpha1::ProtocolType; +using ConfigSerializationType = + envoy::config::filter::network::dubbo_proxy::v2alpha1::SerializationType; class DubboFilterTest : public testing::Test { public: @@ -34,8 +36,8 @@ class DubboFilterTest : public testing::Test { counter->reset(); } - filter_ = std::make_unique("test.", ConfigDubboProxy::Dubbo, ConfigDubboProxy::Hessian2, - store_, timeSystem()); + filter_ = std::make_unique("test.", ConfigProtocolType::Dubbo, + ConfigSerializationType::Hessian2, store_, timeSystem()); filter_->initializeReadFilterCallbacks(read_filter_callbacks_); filter_->onNewConnection(); diff --git a/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc b/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc new file mode 100644 index 000000000000..f5461e9ac071 --- /dev/null +++ b/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc @@ -0,0 +1,441 @@ +#include "envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.pb.h" +#include "envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.pb.validate.h" +#include "envoy/config/filter/network/dubbo_proxy/v2alpha1/route.pb.h" +#include "envoy/config/filter/network/dubbo_proxy/v2alpha1/route.pb.validate.h" + +#include "common/protobuf/protobuf.h" + +#include "extensions/filters/network/dubbo_proxy/router/route_matcher.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace DubboProxy { +namespace Router { + +namespace { + +envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration +parseRouteConfigurationFromV2Yaml(const std::string& yaml) { + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration route_config; + MessageUtil::loadFromYaml(yaml, route_config); + MessageUtil::validate(route_config); + return route_config; +} + +envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy +parseDubboProxyFromV2Yaml(const std::string& yaml) { + envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy config; + MessageUtil::loadFromYaml(yaml, config); + MessageUtil::validate(config); + return config; +} + +} // namespace + +TEST(RouteMatcherTest, RouteByServiceNameWithAnyMethod) { + { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +routes: + - match: + method: + name: + regex: "(.*?)" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + + RouteMatcher matcher(config); + MessageMetadata metadata; + metadata.setMethodName("test"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceName("unknown"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceGroup("test"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceVersion("1.0.0"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + + // Ignore version matches if there is no version field in the configuration information. + metadata.setServiceVersion("1.0.1"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + + metadata.setServiceGroup("test_one"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + } + + // Service name with optional(version and group) matches. + { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +version: 1.0.0 +group: test +routes: + - match: + method: + name: + regex: "(.*?)" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + + RouteMatcher matcher(config); + MessageMetadata metadata; + metadata.setMethodName("test"); + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceGroup("test"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceVersion("1.0.0"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + } + + // Service name with version matches. + { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +version: 1.0.0 +routes: + - match: + method: + name: + regex: "(.*?)" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + + RouteMatcher matcher(config); + MessageMetadata metadata; + metadata.setMethodName("test"); + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceGroup("test"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceVersion("1.0.0"); + EXPECT_NE(nullptr, matcher.route(metadata, 0)); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + + // Ignore group matches if there is no group field in the configuration information. + metadata.setServiceGroup("test_1"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + } + + // Service name with group matches. + { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +group: HSF +routes: + - match: + method: + name: + regex: "(.*?)" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + + RouteMatcher matcher(config); + MessageMetadata metadata; + metadata.setMethodName("test"); + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceGroup("test"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceVersion("1.0.0"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setServiceGroup("HSF"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + } +} + +TEST(RouteMatcherTest, RouteByMethodWithExactMatch) { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +routes: + - match: + method: + name: + exact: "add" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + MessageMetadata metadata; + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + + RouteMatcher matcher(config); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setMethodName("sub"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setMethodName("add"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); +} + +TEST(RouteMatcherTest, RouteByMethodWithSuffixMatch) { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +routes: + - match: + method: + name: + suffix: "test" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + MessageMetadata metadata; + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + + RouteMatcher matcher(config); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setMethodName("sub"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setMethodName("add123test"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); +} + +TEST(RouteMatcherTest, RouteByMethodWithPrefixMatch) { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +routes: + - match: + method: + name: + prefix: "test" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + MessageMetadata metadata; + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + + RouteMatcher matcher(config); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setMethodName("ab12test"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setMethodName("test12d2test"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + + metadata.setMethodName("testme"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); +} + +TEST(RouteMatcherTest, RouteByMethodWithRegexMatch) { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +routes: + - match: + method: + name: + regex: "\\d{3}test" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + MessageMetadata metadata; + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + + RouteMatcher matcher(config); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setMethodName("12test"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + metadata.setMethodName("456test"); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + + metadata.setMethodName("4567test"); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); +} + +TEST(RouteMatcherTest, RouteByParameterWithRangeMatch) { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +routes: + - match: + method: + name: + exact: "add" + params_match: + 0: + range_match: + start: 100 + end: 200 + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + MessageMetadata metadata; + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + metadata.setMethodName("add"); + metadata.addParameterValue(0, "150"); + + RouteMatcher matcher(config); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); +} + +TEST(RouteMatcherTest, RouteByParameterWithExactMatch) { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +routes: + - match: + method: + name: + exact: "add" + params_match: + 1: + exact_match: "user_id:94562" + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + MessageMetadata metadata; + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + metadata.setMethodName("add"); + metadata.addParameterValue(1, "user_id:94562"); + + RouteMatcher matcher(config); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); +} + +TEST(RouteMatcherTest, RouteWithHeaders) { + const std::string yaml = R"EOF( +name: local_route +interface: org.apache.dubbo.demo.DemoService +routes: + - match: + method: + name: + exact: "add" + headers: + - name: custom + exact_match: "123" + - name: custom1 + exact_match: "123" + invert_match: true + route: + cluster: user_service_dubbo_server +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + MessageMetadata metadata; + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + metadata.setMethodName("add"); + metadata.addHeader("custom", "123"); + std::string test_value("123"); + + Envoy::Http::LowerCaseString test_key("custom1"); + metadata.addHeaderReference(test_key, test_value); + + RouteMatcher matcher(config); + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); + + test_value = "456"; + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); + + test_value = "123"; + EXPECT_EQ(nullptr, matcher.route(metadata, 0)); +} + +TEST(MultiRouteMatcherTest, Route) { + const std::string yaml = R"EOF( +stat_prefix: dubbo_incomming_stats +protocol_type: Dubbo +serialization_type: Hessian2 +route_config: + - name: test1 + interface: org.apache.dubbo.demo.DemoService + routes: + - match: + method: + name: + exact: "add" + params_match: + 1: + exact_match: "user_id" + route: + cluster: user_service_dubbo_server + - name: test2 + interface: org.apache.dubbo.demo.FormatService + routes: + - match: + method: + name: + exact: "format" + route: + cluster: format_service +)EOF"; + + envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy config = + parseDubboProxyFromV2Yaml(yaml); + MessageMetadata metadata; + metadata.setServiceName("org.apache.dubbo.demo.DemoService"); + metadata.setMethodName("add"); + metadata.addParameterValue(1, "user_id"); + + MultiRouteMatcher matcher(config.route_config()); + EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName()); +} + +} // namespace Router +} // namespace DubboProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy