Coverage Report

Created: 2023-11-12 09:30

/proc/self/cwd/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/extensions/filters/network/dubbo_proxy/router/route_matcher.h"
2
3
#include "envoy/config/route/v3/route_components.pb.h"
4
#include "envoy/extensions/filters/network/dubbo_proxy/v3/route.pb.h"
5
6
#include "source/common/protobuf/utility.h"
7
8
namespace Envoy {
9
namespace Extensions {
10
namespace NetworkFilters {
11
namespace DubboProxy {
12
namespace Router {
13
14
RouteEntryImplBase::RouteEntryImplBase(
15
    const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route)
16
    : cluster_name_(route.route().cluster()),
17
0
      config_headers_(Http::HeaderUtility::buildHeaderDataVector(route.match().headers())) {
18
0
  if (route.route().has_metadata_match()) {
19
0
    const auto filter_it = route.route().metadata_match().filter_metadata().find(
20
0
        Envoy::Config::MetadataFilters::get().ENVOY_LB);
21
0
    if (filter_it != route.route().metadata_match().filter_metadata().end()) {
22
0
      metadata_match_criteria_ =
23
0
          std::make_unique<Envoy::Router::MetadataMatchCriteriaImpl>(filter_it->second);
24
0
    }
25
0
  }
26
0
  if (route.route().cluster_specifier_case() ==
27
0
      envoy::extensions::filters::network::dubbo_proxy::v3::RouteAction::ClusterSpecifierCase::
28
0
          kWeightedClusters) {
29
0
    total_cluster_weight_ = 0UL;
30
0
    for (const auto& cluster : route.route().weighted_clusters().clusters()) {
31
0
      weighted_clusters_.emplace_back(std::make_shared<WeightedClusterEntry>(*this, cluster));
32
0
      total_cluster_weight_ += weighted_clusters_.back()->clusterWeight();
33
0
    }
34
0
    ENVOY_LOG(debug, "dubbo route matcher: weighted_clusters_size {}", weighted_clusters_.size());
35
0
  }
36
0
}
37
38
0
const std::string& RouteEntryImplBase::clusterName() const { return cluster_name_; }
39
40
0
const RouteEntry* RouteEntryImplBase::routeEntry() const { return this; }
41
42
0
RouteConstSharedPtr RouteEntryImplBase::clusterEntry(uint64_t random_value) const {
43
0
  if (weighted_clusters_.empty()) {
44
0
    ENVOY_LOG(debug, "dubbo route matcher: weighted_clusters_size {}", weighted_clusters_.size());
45
0
    return shared_from_this();
46
0
  }
47
48
0
  return WeightedClusterUtil::pickCluster(weighted_clusters_, total_cluster_weight_, random_value,
49
0
                                          false);
50
0
}
51
52
0
bool RouteEntryImplBase::headersMatch(const RpcInvocationImpl& invocation) const {
53
0
  if (config_headers_.empty()) {
54
0
    ENVOY_LOG(debug, "dubbo route matcher: no headers match");
55
0
    return true;
56
0
  }
57
58
0
  const auto& headers = invocation.attachment().headers();
59
0
  ENVOY_LOG(debug, "dubbo route matcher: headers size {}, metadata headers size {}",
60
0
            config_headers_.size(), headers.size());
61
0
  return Http::HeaderUtility::matchHeaders(headers, config_headers_);
62
0
}
63
64
RouteEntryImplBase::WeightedClusterEntry::WeightedClusterEntry(const RouteEntryImplBase& parent,
65
                                                               const WeightedCluster& cluster)
66
    : parent_(parent), cluster_name_(cluster.name()),
67
0
      cluster_weight_(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, weight)) {
68
0
  if (cluster.has_metadata_match()) {
69
0
    const auto filter_it = cluster.metadata_match().filter_metadata().find(
70
0
        Envoy::Config::MetadataFilters::get().ENVOY_LB);
71
0
    if (filter_it != cluster.metadata_match().filter_metadata().end()) {
72
73
0
      if (parent.metadata_match_criteria_) {
74
0
        metadata_match_criteria_ =
75
0
            parent.metadata_match_criteria_->mergeMatchCriteria(filter_it->second);
76
0
      } else {
77
0
        metadata_match_criteria_ =
78
0
            std::make_unique<Envoy::Router::MetadataMatchCriteriaImpl>(filter_it->second);
79
0
      }
80
0
    }
81
0
  }
82
0
}
83
84
ParameterRouteEntryImpl::ParameterRouteEntryImpl(
85
    const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route)
86
0
    : RouteEntryImplBase(route) {
87
0
  for (auto& config : route.match().method().params_match()) {
88
0
    parameter_data_list_.emplace_back(config.first, config.second);
89
0
  }
90
0
}
91
92
0
ParameterRouteEntryImpl::~ParameterRouteEntryImpl() = default;
93
94
bool ParameterRouteEntryImpl::matchParameter(absl::string_view request_data,
95
0
                                             const ParameterData& config_data) const {
96
0
  switch (config_data.match_type_) {
97
0
  case Http::HeaderUtility::HeaderMatchType::Value:
98
0
    return config_data.value_.empty() || request_data == config_data.value_;
99
0
  case Http::HeaderUtility::HeaderMatchType::Range: {
100
0
    int64_t value = 0;
101
0
    return absl::SimpleAtoi(request_data, &value) && value >= config_data.range_.start() &&
102
0
           value < config_data.range_.end();
103
0
  }
104
0
  default:
105
0
    PANIC("not handled");
106
0
  }
107
0
}
108
109
RouteConstSharedPtr ParameterRouteEntryImpl::matches(const MessageMetadata& metadata,
110
0
                                                     uint64_t random_value) const {
111
0
  ASSERT(metadata.hasInvocationInfo());
112
0
  const auto invocation = dynamic_cast<const RpcInvocationImpl*>(&metadata.invocationInfo());
113
0
  ASSERT(invocation);
114
115
0
  const auto& parameters = invocation->parameters();
116
0
  if (parameters.empty()) {
117
0
    return nullptr;
118
0
  }
119
120
0
  ENVOY_LOG(debug, "dubbo route matcher: parameter name match");
121
0
  for (auto& config_data : parameter_data_list_) {
122
0
    if (config_data.index_ >= parameters.size()) {
123
0
      ENVOY_LOG(debug,
124
0
                "dubbo route matcher: parameter matching failed, there is no parameter in the "
125
0
                "user request, index '{}'",
126
0
                config_data.index_);
127
0
      return nullptr;
128
0
    }
129
130
0
    const auto data = parameters.at(config_data.index_)->toString();
131
0
    if (!data.has_value()) {
132
0
      ENVOY_LOG(debug,
133
0
                "dubbo route matcher: parameter matching failed, the parameter cannot be converted "
134
0
                "to string, index '{}'",
135
0
                config_data.index_);
136
0
      return nullptr;
137
0
    }
138
139
0
    if (!matchParameter(absl::string_view(data.value().get()), config_data)) {
140
0
      ENVOY_LOG(debug, "dubbo route matcher: parameter matching failed, index '{}', value '{}'",
141
0
                config_data.index_, data.value().get());
142
0
      return nullptr;
143
0
    }
144
0
  }
145
146
0
  return clusterEntry(random_value);
147
0
}
148
149
ParameterRouteEntryImpl::ParameterData::ParameterData(uint32_t index,
150
0
                                                      const ParameterMatchSpecifier& config) {
151
0
  index_ = index;
152
0
  switch (config.parameter_match_specifier_case()) {
153
0
  case ParameterMatchSpecifier::kExactMatch:
154
0
    match_type_ = Http::HeaderUtility::HeaderMatchType::Value;
155
0
    value_ = config.exact_match();
156
0
    break;
157
0
  case ParameterMatchSpecifier::kRangeMatch:
158
0
    match_type_ = Http::HeaderUtility::HeaderMatchType::Range;
159
0
    range_.set_start(config.range_match().start());
160
0
    range_.set_end(config.range_match().end());
161
0
    break;
162
0
  default:
163
0
    match_type_ = Http::HeaderUtility::HeaderMatchType::Value;
164
0
    break;
165
0
  }
166
0
}
167
168
MethodRouteEntryImpl::MethodRouteEntryImpl(
169
    const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route)
170
0
    : RouteEntryImplBase(route), method_name_(route.match().method().name()) {
171
0
  if (route.match().method().params_match_size() != 0) {
172
0
    parameter_route_ = std::make_shared<ParameterRouteEntryImpl>(route);
173
0
  }
174
0
}
175
176
0
MethodRouteEntryImpl::~MethodRouteEntryImpl() = default;
177
178
RouteConstSharedPtr MethodRouteEntryImpl::matches(const MessageMetadata& metadata,
179
0
                                                  uint64_t random_value) const {
180
0
  ASSERT(metadata.hasInvocationInfo());
181
0
  const auto invocation = dynamic_cast<const RpcInvocationImpl*>(&metadata.invocationInfo());
182
0
  ASSERT(invocation);
183
184
0
  if (!RouteEntryImplBase::headersMatch(*invocation)) {
185
0
    ENVOY_LOG(error, "dubbo route matcher: headers not match");
186
0
    return nullptr;
187
0
  }
188
189
0
  if (invocation->methodName().empty()) {
190
0
    ENVOY_LOG(error, "dubbo route matcher: there is no method name in the metadata");
191
0
    return nullptr;
192
0
  }
193
194
0
  if (!method_name_.match(invocation->methodName())) {
195
0
    ENVOY_LOG(debug, "dubbo route matcher: method matching failed, input method '{}'",
196
0
              invocation->methodName());
197
0
    return nullptr;
198
0
  }
199
200
0
  if (parameter_route_) {
201
0
    ENVOY_LOG(debug, "dubbo route matcher: parameter matching is required");
202
0
    return parameter_route_->matches(metadata, random_value);
203
0
  }
204
205
0
  return clusterEntry(random_value);
206
0
}
207
208
SingleRouteMatcherImpl::SingleRouteMatcherImpl(const RouteConfig& config,
209
                                               Server::Configuration::ServerFactoryContext&)
210
0
    : interface_matcher_(config.interface()), group_(config.group()), version_(config.version()) {
211
0
  using envoy::extensions::filters::network::dubbo_proxy::v3::RouteMatch;
212
213
0
  for (const auto& route : config.routes()) {
214
0
    routes_.emplace_back(std::make_shared<MethodRouteEntryImpl>(route));
215
0
  }
216
0
  ENVOY_LOG(debug, "dubbo route matcher: routes list size {}", routes_.size());
217
0
}
218
219
0
bool SingleRouteMatcherImpl::matchServiceGroup(const RpcInvocationImpl& invocation) const {
220
0
  if (!group_.has_value() || group_.value().empty()) {
221
0
    return true;
222
0
  }
223
224
0
  return invocation.serviceGroup().has_value() && invocation.serviceGroup().value() == group_;
225
0
}
226
227
0
bool SingleRouteMatcherImpl::matchServiceVersion(const RpcInvocationImpl& invocation) const {
228
0
  if (!version_.has_value() || version_.value().empty()) {
229
0
    return true;
230
0
  }
231
0
  return invocation.serviceVersion().has_value() && invocation.serviceVersion().value() == version_;
232
0
}
233
234
0
bool SingleRouteMatcherImpl::matchServiceName(const RpcInvocationImpl& invocation) const {
235
0
  return interface_matcher_.match(invocation.serviceName());
236
0
}
237
238
0
SingleRouteMatcherImpl::InterfaceMatcher::InterfaceMatcher(const std::string& interface_name) {
239
0
  if (interface_name == "*") {
240
0
    impl_ = [](const absl::string_view interface) { return !interface.empty(); };
241
0
    return;
242
0
  }
243
0
  if (absl::StartsWith(interface_name, "*")) {
244
0
    const std::string suffix = interface_name.substr(1);
245
0
    impl_ = [suffix](const absl::string_view interface) {
246
0
      return interface.size() > suffix.size() && absl::EndsWith(interface, suffix);
247
0
    };
248
0
    return;
249
0
  }
250
0
  if (absl::EndsWith(interface_name, "*")) {
251
0
    const std::string prefix = interface_name.substr(0, interface_name.size() - 1);
252
0
    impl_ = [prefix](const absl::string_view interface) {
253
0
      return interface.size() > prefix.size() && absl::StartsWith(interface, prefix);
254
0
    };
255
0
    return;
256
0
  }
257
0
  impl_ = [interface_name](const absl::string_view interface) {
258
0
    return interface == interface_name;
259
0
  };
260
0
}
261
262
RouteConstSharedPtr SingleRouteMatcherImpl::route(const MessageMetadata& metadata,
263
0
                                                  uint64_t random_value) const {
264
0
  ASSERT(metadata.hasInvocationInfo());
265
0
  const auto invocation = dynamic_cast<const RpcInvocationImpl*>(&metadata.invocationInfo());
266
0
  ASSERT(invocation);
267
268
0
  if (matchServiceName(*invocation) && matchServiceVersion(*invocation) &&
269
0
      matchServiceGroup(*invocation)) {
270
0
    for (const auto& route : routes_) {
271
0
      RouteConstSharedPtr route_entry = route->matches(metadata, random_value);
272
0
      if (nullptr != route_entry) {
273
0
        return route_entry;
274
0
      }
275
0
    }
276
0
  } else {
277
0
    ENVOY_LOG(debug, "dubbo route matcher: interface matching failed");
278
0
  }
279
280
0
  return nullptr;
281
0
}
282
283
RouteConfigImpl::RouteConfigImpl(const RouteConfigList& route_config_list,
284
0
                                 Server::Configuration::ServerFactoryContext& context, bool) {
285
0
  for (const auto& route_config : route_config_list) {
286
0
    route_matcher_list_.emplace_back(
287
0
        std::make_unique<SingleRouteMatcherImpl>(route_config, context));
288
0
  }
289
0
  ENVOY_LOG(debug, "route matcher list size {}", route_matcher_list_.size());
290
0
}
291
292
RouteConstSharedPtr RouteConfigImpl::route(const MessageMetadata& metadata,
293
0
                                           uint64_t random_value) const {
294
0
  for (const auto& route_matcher : route_matcher_list_) {
295
0
    auto route = route_matcher->route(metadata, random_value);
296
0
    if (nullptr != route) {
297
0
      return route;
298
0
    }
299
0
  }
300
301
0
  return nullptr;
302
0
}
303
304
} // namespace Router
305
} // namespace DubboProxy
306
} // namespace NetworkFilters
307
} // namespace Extensions
308
} // namespace Envoy