|  | @@ -22,6 +22,7 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #include "absl/strings/str_cat.h"
 | 
	
		
			
				|  |  |  #include "absl/strings/str_split.h"
 | 
	
		
			
				|  |  | +#include "absl/strings/string_view.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #include <grpc/grpc.h>
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -52,24 +53,26 @@ class XdsRoutingLbConfig : public LoadBalancingPolicy::Config {
 | 
	
		
			
				|  |  |    struct ChildConfig {
 | 
	
		
			
				|  |  |      RefCountedPtr<LoadBalancingPolicy::Config> config;
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  using Matcher = std::pair<std::string, std::string>;
 | 
	
		
			
				|  |  | -  using RouteVector = std::vector<std::pair<Matcher, std::string>>;
 | 
	
		
			
				|  |  | +  struct Matcher {
 | 
	
		
			
				|  |  | +    std::string service;
 | 
	
		
			
				|  |  | +    std::string method;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +  using RouteTable = std::vector<std::pair<Matcher, std::string>>;
 | 
	
		
			
				|  |  |    using ActionMap = std::map<std::string, ChildConfig>;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  explicit XdsRoutingLbConfig(ActionMap action_map, RouteVector route_vector)
 | 
	
		
			
				|  |  | +  XdsRoutingLbConfig(ActionMap action_map, RouteTable route_table)
 | 
	
		
			
				|  |  |        : action_map_(std::move(action_map)),
 | 
	
		
			
				|  |  | -        route_vector_(std::move(route_vector)) {}
 | 
	
		
			
				|  |  | +        route_table_(std::move(route_table)) {}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    const char* name() const override { return kXdsRouting; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    const ActionMap& action_map() const { return action_map_; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  const RouteVector& route_vector() const { return route_vector_; }
 | 
	
		
			
				|  |  | +  const RouteTable& route_table() const { return route_table_; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |   private:
 | 
	
		
			
				|  |  |    ActionMap action_map_;
 | 
	
		
			
				|  |  | -  RouteVector route_vector_;
 | 
	
		
			
				|  |  | +  RouteTable route_table_;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // xds_routing LB policy.
 | 
	
	
		
			
				|  | @@ -87,39 +90,37 @@ class XdsRoutingLb : public LoadBalancingPolicy {
 | 
	
		
			
				|  |  |    // A simple wrapper for ref-counting a picker from the child policy.
 | 
	
		
			
				|  |  |    class ChildPickerWrapper : public RefCounted<ChildPickerWrapper> {
 | 
	
		
			
				|  |  |     public:
 | 
	
		
			
				|  |  | -    explicit ChildPickerWrapper(const std::string& name,
 | 
	
		
			
				|  |  | -                                std::unique_ptr<SubchannelPicker> picker)
 | 
	
		
			
				|  |  | -        : name_(name), picker_(std::move(picker)) {}
 | 
	
		
			
				|  |  | +    ChildPickerWrapper(std::string name,
 | 
	
		
			
				|  |  | +                       std::unique_ptr<SubchannelPicker> picker)
 | 
	
		
			
				|  |  | +        : name_(std::move(name)), picker_(std::move(picker)) {}
 | 
	
		
			
				|  |  |      PickResult Pick(PickArgs args) { return picker_->Pick(std::move(args)); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    std::string name() { return name_; }
 | 
	
		
			
				|  |  | +    const std::string& name() { return name_; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |     private:
 | 
	
		
			
				|  |  |      std::string name_;
 | 
	
		
			
				|  |  |      std::unique_ptr<SubchannelPicker> picker_;
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  // Picks a child using stateless WRR and then delegates to that
 | 
	
		
			
				|  |  | +  // Picks a child using prefix or path matching and then delegates to that
 | 
	
		
			
				|  |  |    // child's picker.
 | 
	
		
			
				|  |  | -  class XdsRoutingPicker : public SubchannelPicker {
 | 
	
		
			
				|  |  | +  class RoutePicker : public SubchannelPicker {
 | 
	
		
			
				|  |  |     public:
 | 
	
		
			
				|  |  | -    // Maintains a xds_routing list of pickers from each child that is in
 | 
	
		
			
				|  |  | -    // ready state. The first element in the pair represents the end of a
 | 
	
		
			
				|  |  | -    // range proportional to the child's weight. The start of the range
 | 
	
		
			
				|  |  | -    // is the previous value in the vector and is 0 for the first element.
 | 
	
		
			
				|  |  | -    using PickerList = InlinedVector<RefCountedPtr<ChildPickerWrapper>, 1>;
 | 
	
		
			
				|  |  | +    struct Route {
 | 
	
		
			
				|  |  | +      XdsRoutingLbConfig::Matcher matcher;
 | 
	
		
			
				|  |  | +      RefCountedPtr<ChildPickerWrapper> picker;
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    using PickerMap = std::map<std::string, RefCountedPtr<ChildPickerWrapper>>;
 | 
	
		
			
				|  |  | +    // Maintains an ordered xds route table as provided by RDS response.
 | 
	
		
			
				|  |  | +    using RouteTable = std::vector<Route>;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    XdsRoutingPicker(RefCountedPtr<XdsRoutingLb> parent, PickerMap pickers)
 | 
	
		
			
				|  |  | -        : parent_(std::move(parent)), pickers_(std::move(pickers)) {}
 | 
	
		
			
				|  |  | -    ~XdsRoutingPicker() { parent_.reset(DEBUG_LOCATION, "XdsRoutingPicker"); }
 | 
	
		
			
				|  |  | +    RoutePicker(RouteTable route_table)
 | 
	
		
			
				|  |  | +        : route_table_(std::move(route_table)) {}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      PickResult Pick(PickArgs args) override;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |     private:
 | 
	
		
			
				|  |  | -    RefCountedPtr<XdsRoutingLb> parent_;
 | 
	
		
			
				|  |  | -    PickerMap pickers_;
 | 
	
		
			
				|  |  | +    RouteTable route_table_;
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    // Each XdsRoutingChild holds a ref to its parent XdsRoutingLb.
 | 
	
	
		
			
				|  | @@ -145,8 +146,6 @@ class XdsRoutingLb : public LoadBalancingPolicy {
 | 
	
		
			
				|  |  |        return picker_wrapper_;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    std::string name() const { return name_; }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |     private:
 | 
	
		
			
				|  |  |      class Helper : public ChannelControlHelper {
 | 
	
		
			
				|  |  |       public:
 | 
	
	
		
			
				|  | @@ -211,35 +210,29 @@ class XdsRoutingLb : public LoadBalancingPolicy {
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  //
 | 
	
		
			
				|  |  | -// XdsRoutingLb::XdsRoutingPicker
 | 
	
		
			
				|  |  | +// XdsRoutingLb::RoutePicker
 | 
	
		
			
				|  |  |  //
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -XdsRoutingLb::PickResult XdsRoutingLb::XdsRoutingPicker::Pick(PickArgs args) {
 | 
	
		
			
				|  |  | -  std::string path;
 | 
	
		
			
				|  |  | +XdsRoutingLb::PickResult XdsRoutingLb::RoutePicker::Pick(PickArgs args) {
 | 
	
		
			
				|  |  | +  absl::string_view path;
 | 
	
		
			
				|  |  |    for (const auto& p : *(args.initial_metadata)) {
 | 
	
		
			
				|  |  | -    if (memcmp(p.first.data(), ":path", static_cast<int>(p.first.size())) ==
 | 
	
		
			
				|  |  | -        0) {
 | 
	
		
			
				|  |  | -      path = std::string(p.second.data(), static_cast<int>(p.second.size()));
 | 
	
		
			
				|  |  | +    if (p.first == ":path") {
 | 
	
		
			
				|  |  | +      path = p.second;
 | 
	
		
			
				|  |  |        break;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  std::vector<std::string> v = absl::StrSplit(path, '/');
 | 
	
		
			
				|  |  | -  GPR_DEBUG_ASSERT(v.size() == 3);
 | 
	
		
			
				|  |  | -  std::string service = v[1];
 | 
	
		
			
				|  |  | -  std::string method = v[2];
 | 
	
		
			
				|  |  | -  for (int i = 0; i < parent_->config_->route_vector().size(); ++i) {
 | 
	
		
			
				|  |  | -    if (service == parent_->config_->route_vector()[i].first.first &&
 | 
	
		
			
				|  |  | -        ("" == parent_->config_->route_vector()[i].first.second ||
 | 
	
		
			
				|  |  | -         method == parent_->config_->route_vector()[i].first.second)) {
 | 
	
		
			
				|  |  | -      auto picker = pickers_.find(parent_->config_->route_vector()[i].second);
 | 
	
		
			
				|  |  | -      if (picker != pickers_.end()) {
 | 
	
		
			
				|  |  | -        gpr_log(GPR_INFO, "XdsRouting Picked: %s for path %s",
 | 
	
		
			
				|  |  | -                picker->first.c_str(), path.c_str());
 | 
	
		
			
				|  |  | -        return picker->second.get()->Pick(args);
 | 
	
		
			
				|  |  | +  std::vector<absl::string_view> v = absl::StrSplit(path.substr(1), '/');
 | 
	
		
			
				|  |  | +  for (int i = 0; i < route_table_.size(); ++i) {
 | 
	
		
			
				|  |  | +    if (v[0] == route_table_[i].matcher.service &&
 | 
	
		
			
				|  |  | +        ("" == route_table_[i].matcher.method ||
 | 
	
		
			
				|  |  | +         v[1] == route_table_[i].matcher.method)) {
 | 
	
		
			
				|  |  | +      auto picker = route_table_[i].picker;
 | 
	
		
			
				|  |  | +      if (picker != nullptr) {
 | 
	
		
			
				|  |  | +        return picker.get()->Pick(args);
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  return pickers_.begin()->second.get()->Pick(args);
 | 
	
		
			
				|  |  | +  return route_table_[route_table_.size() - 1].picker.get()->Pick(args);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  //
 | 
	
	
		
			
				|  | @@ -313,11 +306,7 @@ void XdsRoutingLb::UpdateLocked(UpdateArgs args) {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsRoutingLb::UpdateStateLocked() {
 | 
	
		
			
				|  |  | -  // Construct a new picker which maintains a map of all child pickers
 | 
	
		
			
				|  |  | -  // that are ready. Each child is represented by a portion of the range
 | 
	
		
			
				|  |  | -  // proportional to its weight, such that the total range is the sum of the
 | 
	
		
			
				|  |  | -  // weights of all children.
 | 
	
		
			
				|  |  | -  XdsRoutingPicker::PickerMap picker_map;
 | 
	
		
			
				|  |  | +  std::map<std::string, RefCountedPtr<ChildPickerWrapper>> picker_map;
 | 
	
		
			
				|  |  |    // Also count the number of children in each state, to determine the
 | 
	
		
			
				|  |  |    // overall state.
 | 
	
		
			
				|  |  |    size_t num_connecting = 0;
 | 
	
	
		
			
				|  | @@ -367,10 +356,19 @@ void XdsRoutingLb::UpdateStateLocked() {
 | 
	
		
			
				|  |  |              ConnectivityStateName(connectivity_state));
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    std::unique_ptr<SubchannelPicker> picker;
 | 
	
		
			
				|  |  | +  RoutePicker::RouteTable route_table;
 | 
	
		
			
				|  |  |    switch (connectivity_state) {
 | 
	
		
			
				|  |  |      case GRPC_CHANNEL_READY:
 | 
	
		
			
				|  |  | -      picker = absl::make_unique<XdsRoutingPicker>(
 | 
	
		
			
				|  |  | -          Ref(DEBUG_LOCATION, "XdsRoutingPicker"), std::move(picker_map));
 | 
	
		
			
				|  |  | +      for (int i = 0; i < config_->route_table().size(); ++i) {
 | 
	
		
			
				|  |  | +        RoutePicker::Route route;
 | 
	
		
			
				|  |  | +        route.matcher = config_->route_table()[i].first;
 | 
	
		
			
				|  |  | +        auto child_picker = picker_map.find(config_->route_table()[i].second);
 | 
	
		
			
				|  |  | +        if (child_picker != picker_map.end()) {
 | 
	
		
			
				|  |  | +          route.picker = child_picker->second;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        route_table.push_back(std::move(route));
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      picker = absl::make_unique<RoutePicker>(std::move(route_table));
 | 
	
		
			
				|  |  |        break;
 | 
	
		
			
				|  |  |      case GRPC_CHANNEL_CONNECTING:
 | 
	
		
			
				|  |  |      case GRPC_CHANNEL_IDLE:
 | 
	
	
		
			
				|  | @@ -460,6 +458,7 @@ void XdsRoutingLb::XdsRoutingChild::UpdateLocked(
 | 
	
		
			
				|  |  |    // Update child weight.
 | 
	
		
			
				|  |  |    // Reactivate if needed.
 | 
	
		
			
				|  |  |    if (delayed_removal_timer_callback_pending_) {
 | 
	
		
			
				|  |  | +    delayed_removal_timer_callback_pending_ = false;
 | 
	
		
			
				|  |  |      grpc_timer_cancel(&delayed_removal_timer_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    // Create child policy if needed.
 | 
	
	
		
			
				|  | @@ -530,7 +529,6 @@ void XdsRoutingLb::XdsRoutingChild::OnDelayedRemovalTimerLocked(
 | 
	
		
			
				|  |  |  RefCountedPtr<SubchannelInterface>
 | 
	
		
			
				|  |  |  XdsRoutingLb::XdsRoutingChild::Helper::CreateSubchannel(
 | 
	
		
			
				|  |  |      const grpc_channel_args& args) {
 | 
	
		
			
				|  |  | -  gpr_log(GPR_INFO, "XdsRoutingChild::Helper::CreateSubchannel");
 | 
	
		
			
				|  |  |    if (xds_routing_child_->xds_routing_policy_->shutting_down_) return nullptr;
 | 
	
		
			
				|  |  |    return xds_routing_child_->xds_routing_policy_->channel_control_helper()
 | 
	
		
			
				|  |  |        ->CreateSubchannel(args);
 | 
	
	
		
			
				|  | @@ -538,12 +536,15 @@ XdsRoutingLb::XdsRoutingChild::Helper::CreateSubchannel(
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsRoutingLb::XdsRoutingChild::Helper::UpdateState(
 | 
	
		
			
				|  |  |      grpc_connectivity_state state, std::unique_ptr<SubchannelPicker> picker) {
 | 
	
		
			
				|  |  | -  gpr_log(GPR_INFO, "XdsRoutingChild::Helper::UpdateState %s",
 | 
	
		
			
				|  |  | -          xds_routing_child_->name().c_str());
 | 
	
		
			
				|  |  | +  if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_routing_lb_trace)) {
 | 
	
		
			
				|  |  | +    gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | +            "XdsRoutingChild::Helper::UpdateState child %s, state %d, piker %p",
 | 
	
		
			
				|  |  | +            xds_routing_child_->name_.c_str(), state, picker.get());
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |    if (xds_routing_child_->xds_routing_policy_->shutting_down_) return;
 | 
	
		
			
				|  |  |    // Cache the picker in the XdsRoutingChild.
 | 
	
		
			
				|  |  |    xds_routing_child_->picker_wrapper_ = MakeRefCounted<ChildPickerWrapper>(
 | 
	
		
			
				|  |  | -      xds_routing_child_->name(), std::move(picker));
 | 
	
		
			
				|  |  | +      xds_routing_child_->name_, std::move(picker));
 | 
	
		
			
				|  |  |    // Decide what state to report for aggregation purposes.
 | 
	
		
			
				|  |  |    // If we haven't seen a failure since the last time we were in state
 | 
	
		
			
				|  |  |    // READY, then we report the state change as-is.  However, once we do see
 | 
	
	
		
			
				|  | @@ -607,87 +608,67 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |      if (it == json.object_value().end()) {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  |            "field:actions error:required field not present"));
 | 
	
		
			
				|  |  | -    } else if (it->second.type() != Json::Type::ARRAY) {
 | 
	
		
			
				|  |  | +    } else if (it->second.type() != Json::Type::OBJECT) {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -          "field:actions error:type should be array"));
 | 
	
		
			
				|  |  | +          "field:actions error:type should be object"));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | -      for (const auto& p : it->second.array_value()) {
 | 
	
		
			
				|  |  | -        auto it_cds = p.object_value().find("cds");
 | 
	
		
			
				|  |  | -        auto it_weighted_target = p.object_value().find("weighted_target");
 | 
	
		
			
				|  |  | -        if (it_cds == p.object_value().end() &&
 | 
	
		
			
				|  |  | -            it_weighted_target == p.object_value().end()) {
 | 
	
		
			
				|  |  | -          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -              "field:actions error: each action needs to be either cds or "
 | 
	
		
			
				|  |  | -              "weighted target"));
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        auto it_name =
 | 
	
		
			
				|  |  | -            (it_cds == p.object_value().end() ? it_weighted_target : it_cds);
 | 
	
		
			
				|  |  | -        auto it_child_policy = p.object_value().find("child_policy");
 | 
	
		
			
				|  |  | -        if (it_child_policy == p.object_value().end()) {
 | 
	
		
			
				|  |  | -          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -              "field:actions error: each action needs child policies"));
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +      for (const auto& p : it->second.object_value()) {
 | 
	
		
			
				|  |  |          XdsRoutingLbConfig::ChildConfig child_config;
 | 
	
		
			
				|  |  |          std::vector<grpc_error*> child_errors =
 | 
	
		
			
				|  |  | -            ParseChildConfig(it_child_policy->second, &child_config);
 | 
	
		
			
				|  |  | +            ParseChildConfig(p.second, &child_config);
 | 
	
		
			
				|  |  |          if (!child_errors.empty()) {
 | 
	
		
			
				|  |  |            // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
 | 
	
		
			
				|  |  |            // string is not static in this case.
 | 
	
		
			
				|  |  |            grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(
 | 
	
		
			
				|  |  | -              absl::StrCat("field:actions name:",
 | 
	
		
			
				|  |  | -                           it_name->second.string_value())
 | 
	
		
			
				|  |  | -                  .c_str());
 | 
	
		
			
				|  |  | +              absl::StrCat("field:actions name:", p.first).c_str());
 | 
	
		
			
				|  |  |            for (grpc_error* child_error : child_errors) {
 | 
	
		
			
				|  |  |              error = grpc_error_add_child(error, child_error);
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  |            error_list.push_back(error);
 | 
	
		
			
				|  |  |          } else {
 | 
	
		
			
				|  |  | -          action_map[it_name->second.string_value()] = std::move(child_config);
 | 
	
		
			
				|  |  | +          action_map[p.first] = std::move(child_config);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    XdsRoutingLbConfig::RouteVector route_vector;
 | 
	
		
			
				|  |  | -    auto route_iter = json.object_value().find("routes");
 | 
	
		
			
				|  |  | -    if (route_iter == json.object_value().end()) {
 | 
	
		
			
				|  |  | -      gpr_log(GPR_INFO, "No routes specified");
 | 
	
		
			
				|  |  | -    } else if (route_iter->second.type() != Json::Type::ARRAY) {
 | 
	
		
			
				|  |  | +    XdsRoutingLbConfig::RouteTable route_table;
 | 
	
		
			
				|  |  | +    it = json.object_value().find("routes");
 | 
	
		
			
				|  |  | +    if (it == json.object_value().end()) {
 | 
	
		
			
				|  |  | +      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +          "field:routes error:required field not present"));
 | 
	
		
			
				|  |  | +    } else if (it->second.type() != Json::Type::ARRAY) {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  |            "field:routes error:type should be array"));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | -      for (const auto& p : route_iter->second.array_value()) {
 | 
	
		
			
				|  |  | -        auto method_name = p.object_value().find("methodName");
 | 
	
		
			
				|  |  | -        if (method_name == p.object_value().end()) {
 | 
	
		
			
				|  |  | -          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -              "field:routes error:methodName is required"));
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -          auto action_name = p.object_value().find("action");
 | 
	
		
			
				|  |  | -          if (action_name == p.object_value().end()) {
 | 
	
		
			
				|  |  | -            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -                "field:routes error:action is required"));
 | 
	
		
			
				|  |  | -          } else {
 | 
	
		
			
				|  |  | -            XdsRoutingLbConfig::Matcher matcher;
 | 
	
		
			
				|  |  | -            auto service = method_name->second.object_value().find("service");
 | 
	
		
			
				|  |  | -            auto method = method_name->second.object_value().find("method");
 | 
	
		
			
				|  |  | -            if (service == method_name->second.object_value().end() &&
 | 
	
		
			
				|  |  | -                method != method_name->second.object_value().end()) {
 | 
	
		
			
				|  |  | -              error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -                  "field:methodName error: service is empty when method is "
 | 
	
		
			
				|  |  | -                  "not"));
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            if (service != method_name->second.object_value().end()) {
 | 
	
		
			
				|  |  | -              matcher.first = service->second.string_value();
 | 
	
		
			
				|  |  | -            } else {
 | 
	
		
			
				|  |  | -              matcher.first = "";
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            if (method != method_name->second.object_value().end()) {
 | 
	
		
			
				|  |  | -              matcher.second = method->second.string_value();
 | 
	
		
			
				|  |  | -            } else {
 | 
	
		
			
				|  |  | -              matcher.first = "";
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            route_vector.emplace_back(matcher,
 | 
	
		
			
				|  |  | -                                      action_name->second.string_value());
 | 
	
		
			
				|  |  | +      for (const auto& route : it->second.array_value()) {
 | 
	
		
			
				|  |  | +        // Parse methodName.
 | 
	
		
			
				|  |  | +        XdsRoutingLbConfig::Matcher matcher;
 | 
	
		
			
				|  |  | +        std::vector<grpc_error*> route_errors =
 | 
	
		
			
				|  |  | +            ParseRouteConfig(route.object_value(), &matcher);
 | 
	
		
			
				|  |  | +        if (!route_errors.empty()) {
 | 
	
		
			
				|  |  | +          // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
 | 
	
		
			
				|  |  | +          // string is not static in this case.
 | 
	
		
			
				|  |  | +          grpc_error* error =
 | 
	
		
			
				|  |  | +              GRPC_ERROR_CREATE_FROM_COPIED_STRING("field:routes error");
 | 
	
		
			
				|  |  | +          for (grpc_error* route_error : route_errors) {
 | 
	
		
			
				|  |  | +            error = grpc_error_add_child(error, route_error);
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  | +          error_list.push_back(error);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +        // Parse action.
 | 
	
		
			
				|  |  | +        std::string cluster_name;
 | 
	
		
			
				|  |  | +        std::vector<grpc_error*> action_errors =
 | 
	
		
			
				|  |  | +            ParseActionConfig(route.object_value(), &cluster_name);
 | 
	
		
			
				|  |  | +        if (!action_errors.empty()) {
 | 
	
		
			
				|  |  | +          // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
 | 
	
		
			
				|  |  | +          // string is not static in this case.
 | 
	
		
			
				|  |  | +          grpc_error* error =
 | 
	
		
			
				|  |  | +              GRPC_ERROR_CREATE_FROM_COPIED_STRING("field:actions error:");
 | 
	
		
			
				|  |  | +          for (grpc_error* action_error : action_errors) {
 | 
	
		
			
				|  |  | +            error = grpc_error_add_child(error, action_error);
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          error_list.push_back(error);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        route_table.emplace_back(std::move(matcher), std::move(cluster_name));
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      if (!error_list.empty()) {
 | 
	
	
		
			
				|  | @@ -696,28 +677,92 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |        return nullptr;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      return MakeRefCounted<XdsRoutingLbConfig>(std::move(action_map),
 | 
	
		
			
				|  |  | -                                              std::move(route_vector));
 | 
	
		
			
				|  |  | +                                              std::move(route_table));
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |   private:
 | 
	
		
			
				|  |  |    static std::vector<grpc_error*> ParseChildConfig(
 | 
	
		
			
				|  |  |        const Json& json, XdsRoutingLbConfig::ChildConfig* child_config) {
 | 
	
		
			
				|  |  |      std::vector<grpc_error*> error_list;
 | 
	
		
			
				|  |  | -    if (json.type() != Json::Type::ARRAY) {
 | 
	
		
			
				|  |  | +    if (json.type() != Json::Type::OBJECT) {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -          "value should be of type array"));
 | 
	
		
			
				|  |  | +          "value should be of type object"));
 | 
	
		
			
				|  |  |        return error_list;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    grpc_error* parse_error = GRPC_ERROR_NONE;
 | 
	
		
			
				|  |  | -    child_config->config =
 | 
	
		
			
				|  |  | -        LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(
 | 
	
		
			
				|  |  | -            json.array_value(), &parse_error);
 | 
	
		
			
				|  |  | -    if (child_config->config == nullptr) {
 | 
	
		
			
				|  |  | -      GPR_DEBUG_ASSERT(parse_error != GRPC_ERROR_NONE);
 | 
	
		
			
				|  |  | -      std::vector<grpc_error*> child_errors;
 | 
	
		
			
				|  |  | -      child_errors.push_back(parse_error);
 | 
	
		
			
				|  |  | +    auto it = json.object_value().find("child_policy");
 | 
	
		
			
				|  |  | +    if (it != json.object_value().end()) {
 | 
	
		
			
				|  |  | +      grpc_error* parse_error = GRPC_ERROR_NONE;
 | 
	
		
			
				|  |  | +      child_config->config =
 | 
	
		
			
				|  |  | +          LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(it->second,
 | 
	
		
			
				|  |  | +                                                                &parse_error);
 | 
	
		
			
				|  |  | +      if (child_config->config == nullptr) {
 | 
	
		
			
				|  |  | +        GPR_DEBUG_ASSERT(parse_error != GRPC_ERROR_NONE);
 | 
	
		
			
				|  |  | +        std::vector<grpc_error*> child_errors;
 | 
	
		
			
				|  |  | +        child_errors.push_back(parse_error);
 | 
	
		
			
				|  |  | +        error_list.push_back(
 | 
	
		
			
				|  |  | +            GRPC_ERROR_CREATE_FROM_VECTOR("field:childPolicy", &child_errors));
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  |        error_list.push_back(
 | 
	
		
			
				|  |  | -          GRPC_ERROR_CREATE_FROM_VECTOR("field:childPolicy", &child_errors));
 | 
	
		
			
				|  |  | +          GRPC_ERROR_CREATE_FROM_STATIC_STRING("did not find childPolicy"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return error_list;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  static std::vector<grpc_error*> ParseRouteConfig(
 | 
	
		
			
				|  |  | +      const Json& json, XdsRoutingLbConfig::Matcher* route_config) {
 | 
	
		
			
				|  |  | +    std::vector<grpc_error*> error_list;
 | 
	
		
			
				|  |  | +    if (json.type() != Json::Type::OBJECT) {
 | 
	
		
			
				|  |  | +      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +          "value should be of type object"));
 | 
	
		
			
				|  |  | +      return error_list;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    auto method_name = json.object_value().find("methodName");
 | 
	
		
			
				|  |  | +    if (method_name == json.object_value().end()) {
 | 
	
		
			
				|  |  | +      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +          "field:routes error:methodName is required"));
 | 
	
		
			
				|  |  | +    } else if (method_name->second.type() != Json::Type::OBJECT) {
 | 
	
		
			
				|  |  | +      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +          "field:routes error:methodName error: type should be object"));
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      auto service = method_name->second.object_value().find("service");
 | 
	
		
			
				|  |  | +      auto method = method_name->second.object_value().find("method");
 | 
	
		
			
				|  |  | +      if (service != method_name->second.object_value().end()) {
 | 
	
		
			
				|  |  | +        route_config->service = service->second.string_value();
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        route_config->service = "";
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if (method != method_name->second.object_value().end()) {
 | 
	
		
			
				|  |  | +        route_config->method = method->second.string_value();
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        route_config->method = "";
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if ((route_config->service == "") && (route_config->method != "")) {
 | 
	
		
			
				|  |  | +        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +            "field:methodName error: service is empty when method is "
 | 
	
		
			
				|  |  | +            "not"));
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return error_list;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  static std::vector<grpc_error*> ParseActionConfig(const Json& json,
 | 
	
		
			
				|  |  | +                                                    std::string* cluster_name) {
 | 
	
		
			
				|  |  | +    std::vector<grpc_error*> error_list;
 | 
	
		
			
				|  |  | +    if (json.type() != Json::Type::OBJECT) {
 | 
	
		
			
				|  |  | +      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +          "value should be of type object"));
 | 
	
		
			
				|  |  | +      return error_list;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    auto action_name = json.object_value().find("action");
 | 
	
		
			
				|  |  | +    if (action_name == json.object_value().end()) {
 | 
	
		
			
				|  |  | +      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +          "field:routes error:action is required"));
 | 
	
		
			
				|  |  | +    } else if (action_name->second.type() != Json::Type::STRING) {
 | 
	
		
			
				|  |  | +      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +          "field:methodName error:type should be string"));
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      *cluster_name = action_name->second.string_value();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      return error_list;
 | 
	
		
			
				|  |  |    }
 |