|  | @@ -50,15 +50,17 @@ constexpr char kXdsRouting[] = "xds_routing_experimental";
 | 
	
		
			
				|  |  |  // Config for xds_routing LB policy.
 | 
	
		
			
				|  |  |  class XdsRoutingLbConfig : public LoadBalancingPolicy::Config {
 | 
	
		
			
				|  |  |   public:
 | 
	
		
			
				|  |  | -  struct ChildConfig {
 | 
	
		
			
				|  |  | -    RefCountedPtr<LoadBalancingPolicy::Config> config;
 | 
	
		
			
				|  |  | -  };
 | 
	
		
			
				|  |  |    struct Matcher {
 | 
	
		
			
				|  |  |      std::string service;
 | 
	
		
			
				|  |  |      std::string method;
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  | -  using RouteTable = std::vector<std::pair<Matcher, std::string>>;
 | 
	
		
			
				|  |  | -  using ActionMap = std::map<std::string, ChildConfig>;
 | 
	
		
			
				|  |  | +  struct Route {
 | 
	
		
			
				|  |  | +    Matcher matcher;
 | 
	
		
			
				|  |  | +    std::string action;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +  using RouteTable = std::vector<Route>;
 | 
	
		
			
				|  |  | +  using ActionMap =
 | 
	
		
			
				|  |  | +      std::map<std::string, RefCountedPtr<LoadBalancingPolicy::Config>>;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    XdsRoutingLbConfig(ActionMap action_map, RouteTable route_table)
 | 
	
		
			
				|  |  |        : action_map_(std::move(action_map)),
 | 
	
	
		
			
				|  | @@ -95,7 +97,7 @@ class XdsRoutingLb : public LoadBalancingPolicy {
 | 
	
		
			
				|  |  |          : name_(std::move(name)), picker_(std::move(picker)) {}
 | 
	
		
			
				|  |  |      PickResult Pick(PickArgs args) { return picker_->Pick(std::move(args)); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    const std::string& name() { return name_; }
 | 
	
		
			
				|  |  | +    const std::string& name() const { return name_; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |     private:
 | 
	
		
			
				|  |  |      std::string name_;
 | 
	
	
		
			
				|  | @@ -114,7 +116,7 @@ class XdsRoutingLb : public LoadBalancingPolicy {
 | 
	
		
			
				|  |  |      // Maintains an ordered xds route table as provided by RDS response.
 | 
	
		
			
				|  |  |      using RouteTable = std::vector<Route>;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    RoutePicker(RouteTable route_table)
 | 
	
		
			
				|  |  | +    explicit RoutePicker(RouteTable route_table)
 | 
	
		
			
				|  |  |          : route_table_(std::move(route_table)) {}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      PickResult Pick(PickArgs args) override;
 | 
	
	
		
			
				|  | @@ -132,7 +134,7 @@ class XdsRoutingLb : public LoadBalancingPolicy {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      void Orphan() override;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    void UpdateLocked(const XdsRoutingLbConfig::ChildConfig& config,
 | 
	
		
			
				|  |  | +    void UpdateLocked(RefCountedPtr<LoadBalancingPolicy::Config> config,
 | 
	
		
			
				|  |  |                        const ServerAddressList& addresses,
 | 
	
		
			
				|  |  |                        const grpc_channel_args* args);
 | 
	
		
			
				|  |  |      void ExitIdleLocked();
 | 
	
	
		
			
				|  | @@ -221,18 +223,24 @@ XdsRoutingLb::PickResult XdsRoutingLb::RoutePicker::Pick(PickArgs args) {
 | 
	
		
			
				|  |  |        break;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  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);
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | +  std::vector<absl::string_view> path_elements =
 | 
	
		
			
				|  |  | +      absl::StrSplit(path.substr(1), '/');
 | 
	
		
			
				|  |  | +  for (const Route& route : route_table_) {
 | 
	
		
			
				|  |  | +    if ((path_elements[0] == route.matcher.service &&
 | 
	
		
			
				|  |  | +         (path_elements[1] == route.matcher.method ||
 | 
	
		
			
				|  |  | +          "" == route.matcher.method)) ||
 | 
	
		
			
				|  |  | +        ("" == route.matcher.service && "" == route.matcher.method)) {
 | 
	
		
			
				|  |  | +      return route.picker.get()->Pick(args);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  return route_table_[route_table_.size() - 1].picker.get()->Pick(args);
 | 
	
		
			
				|  |  | +  PickResult result;
 | 
	
		
			
				|  |  | +  result.type = PickResult::PICK_FAILED;
 | 
	
		
			
				|  |  | +  result.error =
 | 
	
		
			
				|  |  | +      grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +                             "xds routing picker not given any picker; default "
 | 
	
		
			
				|  |  | +                             "route not configured"),
 | 
	
		
			
				|  |  | +                         GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_INTERNAL);
 | 
	
		
			
				|  |  | +  return result;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  //
 | 
	
	
		
			
				|  | @@ -294,7 +302,7 @@ void XdsRoutingLb::UpdateLocked(UpdateArgs args) {
 | 
	
		
			
				|  |  |    // Add or update the actions in the new config.
 | 
	
		
			
				|  |  |    for (const auto& p : config_->action_map()) {
 | 
	
		
			
				|  |  |      const std::string& name = p.first;
 | 
	
		
			
				|  |  | -    const XdsRoutingLbConfig::ChildConfig& config = p.second;
 | 
	
		
			
				|  |  | +    RefCountedPtr<LoadBalancingPolicy::Config> config = p.second;
 | 
	
		
			
				|  |  |      auto it = actions_.find(name);
 | 
	
		
			
				|  |  |      if (it == actions_.end()) {
 | 
	
		
			
				|  |  |        it = actions_.emplace(std::make_pair(name, nullptr)).first;
 | 
	
	
		
			
				|  | @@ -306,9 +314,9 @@ void XdsRoutingLb::UpdateLocked(UpdateArgs args) {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsRoutingLb::UpdateStateLocked() {
 | 
	
		
			
				|  |  | -  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_ready = 0;
 | 
	
		
			
				|  |  |    size_t num_connecting = 0;
 | 
	
		
			
				|  |  |    size_t num_idle = 0;
 | 
	
		
			
				|  |  |    size_t num_transient_failures = 0;
 | 
	
	
		
			
				|  | @@ -321,7 +329,7 @@ void XdsRoutingLb::UpdateStateLocked() {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      switch (child->connectivity_state()) {
 | 
	
		
			
				|  |  |        case GRPC_CHANNEL_READY: {
 | 
	
		
			
				|  |  | -        picker_map[child_name] = child->picker_wrapper();
 | 
	
		
			
				|  |  | +        ++num_ready;
 | 
	
		
			
				|  |  |          break;
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        case GRPC_CHANNEL_CONNECTING: {
 | 
	
	
		
			
				|  | @@ -342,7 +350,7 @@ void XdsRoutingLb::UpdateStateLocked() {
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    // Determine aggregated connectivity state.
 | 
	
		
			
				|  |  |    grpc_connectivity_state connectivity_state;
 | 
	
		
			
				|  |  | -  if (picker_map.size() > 0) {
 | 
	
		
			
				|  |  | +  if (num_ready > 0) {
 | 
	
		
			
				|  |  |      connectivity_state = GRPC_CHANNEL_READY;
 | 
	
		
			
				|  |  |    } else if (num_connecting > 0) {
 | 
	
		
			
				|  |  |      connectivity_state = GRPC_CHANNEL_CONNECTING;
 | 
	
	
		
			
				|  | @@ -356,20 +364,30 @@ void XdsRoutingLb::UpdateStateLocked() {
 | 
	
		
			
				|  |  |              ConnectivityStateName(connectivity_state));
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    std::unique_ptr<SubchannelPicker> picker;
 | 
	
		
			
				|  |  | -  RoutePicker::RouteTable route_table;
 | 
	
		
			
				|  |  |    switch (connectivity_state) {
 | 
	
		
			
				|  |  | -    case GRPC_CHANNEL_READY:
 | 
	
		
			
				|  |  | +    case GRPC_CHANNEL_READY: {
 | 
	
		
			
				|  |  | +      RoutePicker::RouteTable route_table;
 | 
	
		
			
				|  |  |        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.matcher = config_->route_table()[i].matcher;
 | 
	
		
			
				|  |  | +        auto it = actions_.find(config_->route_table()[i].action);
 | 
	
		
			
				|  |  | +        if (it != actions_.end()) {
 | 
	
		
			
				|  |  | +          route.picker = it->second->picker_wrapper();
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | +                  "[xds_routing_lb %p] child policy may have mis-behaved and "
 | 
	
		
			
				|  |  | +                  "did not return a picker, creating a QueuePicker for %s",
 | 
	
		
			
				|  |  | +                  this, config_->route_table()[i].action.c_str());
 | 
	
		
			
				|  |  | +          route.picker = MakeRefCounted<ChildPickerWrapper>(
 | 
	
		
			
				|  |  | +              config_->route_table()[i].action,
 | 
	
		
			
				|  |  | +              absl::make_unique<QueuePicker>(
 | 
	
		
			
				|  |  | +                  Ref(DEBUG_LOCATION, "QueuePicker")));
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          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:
 | 
	
		
			
				|  |  |        picker =
 | 
	
	
		
			
				|  | @@ -452,7 +470,7 @@ XdsRoutingLb::XdsRoutingChild::CreateChildPolicyLocked(
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsRoutingLb::XdsRoutingChild::UpdateLocked(
 | 
	
		
			
				|  |  | -    const XdsRoutingLbConfig::ChildConfig& config,
 | 
	
		
			
				|  |  | +    RefCountedPtr<LoadBalancingPolicy::Config> config,
 | 
	
		
			
				|  |  |      const ServerAddressList& addresses, const grpc_channel_args* args) {
 | 
	
		
			
				|  |  |    if (xds_routing_policy_->shutting_down_) return;
 | 
	
		
			
				|  |  |    // Update child weight.
 | 
	
	
		
			
				|  | @@ -467,7 +485,7 @@ void XdsRoutingLb::XdsRoutingChild::UpdateLocked(
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    // Construct update args.
 | 
	
		
			
				|  |  |    UpdateArgs update_args;
 | 
	
		
			
				|  |  | -  update_args.config = config.config;
 | 
	
		
			
				|  |  | +  update_args.config = config;
 | 
	
		
			
				|  |  |    update_args.addresses = addresses;
 | 
	
		
			
				|  |  |    update_args.args = grpc_channel_args_copy(args);
 | 
	
		
			
				|  |  |    // Update the policy.
 | 
	
	
		
			
				|  | @@ -538,8 +556,10 @@ void XdsRoutingLb::XdsRoutingChild::Helper::UpdateState(
 | 
	
		
			
				|  |  |      grpc_connectivity_state state, std::unique_ptr<SubchannelPicker> picker) {
 | 
	
		
			
				|  |  |    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());
 | 
	
		
			
				|  |  | +            "[xds_routing_lb %p] child %s: received update: state=%s picker=%p",
 | 
	
		
			
				|  |  | +            xds_routing_child_->xds_routing_policy_.get(),
 | 
	
		
			
				|  |  | +            xds_routing_child_->name_.c_str(), ConnectivityStateName(state),
 | 
	
		
			
				|  |  | +            picker.get());
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    if (xds_routing_child_->xds_routing_policy_->shutting_down_) return;
 | 
	
		
			
				|  |  |    // Cache the picker in the XdsRoutingChild.
 | 
	
	
		
			
				|  | @@ -604,6 +624,7 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |      std::vector<grpc_error*> error_list;
 | 
	
		
			
				|  |  |      // action map.
 | 
	
		
			
				|  |  |      XdsRoutingLbConfig::ActionMap action_map;
 | 
	
		
			
				|  |  | +    std::set<std::string /*action_name*/> action_in_use_set;
 | 
	
		
			
				|  |  |      auto it = json.object_value().find("actions");
 | 
	
		
			
				|  |  |      if (it == json.object_value().end()) {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
	
		
			
				|  | @@ -613,7 +634,7 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |            "field:actions error:type should be object"));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |        for (const auto& p : it->second.object_value()) {
 | 
	
		
			
				|  |  | -        XdsRoutingLbConfig::ChildConfig child_config;
 | 
	
		
			
				|  |  | +        RefCountedPtr<LoadBalancingPolicy::Config> child_config;
 | 
	
		
			
				|  |  |          std::vector<grpc_error*> child_errors =
 | 
	
		
			
				|  |  |              ParseChildConfig(p.second, &child_config);
 | 
	
		
			
				|  |  |          if (!child_errors.empty()) {
 | 
	
	
		
			
				|  | @@ -630,6 +651,10 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    if (action_map.size() == 0) {
 | 
	
		
			
				|  |  | +      error_list.push_back(
 | 
	
		
			
				|  |  | +          GRPC_ERROR_CREATE_FROM_COPIED_STRING("no valid actions configured"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |      XdsRoutingLbConfig::RouteTable route_table;
 | 
	
		
			
				|  |  |      it = json.object_value().find("routes");
 | 
	
		
			
				|  |  |      if (it == json.object_value().end()) {
 | 
	
	
		
			
				|  | @@ -639,36 +664,76 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  |            "field:routes error:type should be array"));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | -      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);
 | 
	
		
			
				|  |  | +      const Json::Array& array = it->second.array_value();
 | 
	
		
			
				|  |  | +      for (size_t i = 0; i < array.size(); ++i) {
 | 
	
		
			
				|  |  | +        const Json& element = array[i];
 | 
	
		
			
				|  |  | +        if (element.type() != Json::Type::OBJECT) {
 | 
	
		
			
				|  |  | +          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +              absl::StrCat("filed: routes element: ", i,
 | 
	
		
			
				|  |  | +                           " should be of type object")
 | 
	
		
			
				|  |  | +                  .c_str()));
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          XdsRoutingLbConfig::Route route;
 | 
	
		
			
				|  |  | +          // Parse MethodName.
 | 
	
		
			
				|  |  | +          auto it = element.object_value().find("methodName");
 | 
	
		
			
				|  |  | +          if (it == json.object_value().end()) {
 | 
	
		
			
				|  |  | +            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +                absl::StrCat("field:routes element: ", i,
 | 
	
		
			
				|  |  | +                             " methodName is required")
 | 
	
		
			
				|  |  | +                    .c_str()));
 | 
	
		
			
				|  |  | +          } else if (it->second.type() != Json::Type::OBJECT) {
 | 
	
		
			
				|  |  | +            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +                absl::StrCat("field:routes element: ", i,
 | 
	
		
			
				|  |  | +                             " methodName type should be object")
 | 
	
		
			
				|  |  | +                    .c_str()));
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            std::vector<grpc_error*> route_errors =
 | 
	
		
			
				|  |  | +                ParseRouteConfig(it->second, &route.matcher);
 | 
	
		
			
				|  |  | +            if (!route_errors.empty()) {
 | 
	
		
			
				|  |  | +              grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(
 | 
	
		
			
				|  |  | +                  absl::StrCat("field:routes element: ", i, " error").c_str());
 | 
	
		
			
				|  |  | +              for (grpc_error* route_error : route_errors) {
 | 
	
		
			
				|  |  | +                error = grpc_error_add_child(error, route_error);
 | 
	
		
			
				|  |  | +              }
 | 
	
		
			
				|  |  | +              error_list.push_back(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);
 | 
	
		
			
				|  |  | +          // Parse action.
 | 
	
		
			
				|  |  | +          it = element.object_value().find("action");
 | 
	
		
			
				|  |  | +          if (it == json.object_value().end()) {
 | 
	
		
			
				|  |  | +            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +                absl::StrCat("field:routes element: ", i, " action is required")
 | 
	
		
			
				|  |  | +                    .c_str()));
 | 
	
		
			
				|  |  | +          } else if (it->second.type() != Json::Type::STRING) {
 | 
	
		
			
				|  |  | +            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +                absl::StrCat("field:routes element: ", i,
 | 
	
		
			
				|  |  | +                             " action type should be string")
 | 
	
		
			
				|  |  | +                    .c_str()));
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            route.action = it->second.string_value();
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  | -          error_list.push_back(error);
 | 
	
		
			
				|  |  | +          // Validate action exists and mark it as used.
 | 
	
		
			
				|  |  | +          if (action_map.find(route.action) == action_map.end()) {
 | 
	
		
			
				|  |  | +            grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +                absl::StrCat("action ", route.action, " does not exist")
 | 
	
		
			
				|  |  | +                    .c_str());
 | 
	
		
			
				|  |  | +            error_list.push_back(error);
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            action_in_use_set.insert(route.action);
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          route_table.emplace_back(std::move(route));
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        route_table.emplace_back(std::move(matcher), std::move(cluster_name));
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if (route_table.size() == 0) {
 | 
	
		
			
				|  |  | +      grpc_error* error =
 | 
	
		
			
				|  |  | +          GRPC_ERROR_CREATE_FROM_STATIC_STRING("no valid routes configured");
 | 
	
		
			
				|  |  | +      error_list.push_back(error);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    for (const auto& action : action_map) {
 | 
	
		
			
				|  |  | +      if (action_in_use_set.find(action.first) == action_in_use_set.end()) {
 | 
	
		
			
				|  |  | +        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | +            absl::StrCat("action ", action.first, " is never used").c_str()));
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      if (!error_list.empty()) {
 | 
	
	
		
			
				|  | @@ -682,7 +747,8 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |   private:
 | 
	
		
			
				|  |  |    static std::vector<grpc_error*> ParseChildConfig(
 | 
	
		
			
				|  |  | -      const Json& json, XdsRoutingLbConfig::ChildConfig* child_config) {
 | 
	
		
			
				|  |  | +      const Json& json,
 | 
	
		
			
				|  |  | +      RefCountedPtr<LoadBalancingPolicy::Config>* child_config) {
 | 
	
		
			
				|  |  |      std::vector<grpc_error*> error_list;
 | 
	
		
			
				|  |  |      if (json.type() != Json::Type::OBJECT) {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
	
		
			
				|  | @@ -690,21 +756,20 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |        return error_list;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      auto it = json.object_value().find("child_policy");
 | 
	
		
			
				|  |  | -    if (it != json.object_value().end()) {
 | 
	
		
			
				|  |  | +    if (it == json.object_value().end()) {
 | 
	
		
			
				|  |  | +      error_list.push_back(
 | 
	
		
			
				|  |  | +          GRPC_ERROR_CREATE_FROM_STATIC_STRING("did not find childPolicy"));
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  |        grpc_error* parse_error = GRPC_ERROR_NONE;
 | 
	
		
			
				|  |  | -      child_config->config =
 | 
	
		
			
				|  |  | -          LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(it->second,
 | 
	
		
			
				|  |  | -                                                                &parse_error);
 | 
	
		
			
				|  |  | -      if (child_config->config == nullptr) {
 | 
	
		
			
				|  |  | +      *child_config = LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(
 | 
	
		
			
				|  |  | +          it->second, &parse_error);
 | 
	
		
			
				|  |  | +      if (*child_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_STATIC_STRING("did not find childPolicy"));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      return error_list;
 | 
	
		
			
				|  |  |    }
 | 
	
	
		
			
				|  | @@ -712,57 +777,32 @@ class XdsRoutingLbFactory : public LoadBalancingPolicyFactory {
 | 
	
		
			
				|  |  |    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()) {
 | 
	
		
			
				|  |  | +    // Parse service
 | 
	
		
			
				|  |  | +    auto it = json.object_value().find("service");
 | 
	
		
			
				|  |  | +    if (it == 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) {
 | 
	
		
			
				|  |  | +          "field:service error: required field not present"));
 | 
	
		
			
				|  |  | +    } else if (it->second.type() != Json::Type::STRING) {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -          "field:routes error:methodName error: type should be object"));
 | 
	
		
			
				|  |  | +          "field:service error: should be string"));
 | 
	
		
			
				|  |  |      } 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"));
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | +      route_config->service = it->second.string_value();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    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()) {
 | 
	
		
			
				|  |  | +    // Parse method
 | 
	
		
			
				|  |  | +    it = json.object_value().find("method");
 | 
	
		
			
				|  |  | +    if (it == 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) {
 | 
	
		
			
				|  |  | +          "field:method error: required field not present"));
 | 
	
		
			
				|  |  | +    } else if (it->second.type() != Json::Type::STRING) {
 | 
	
		
			
				|  |  |        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -          "field:methodName error:type should be string"));
 | 
	
		
			
				|  |  | +          "field:method error: should be string"));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | -      *cluster_name = action_name->second.string_value();
 | 
	
		
			
				|  |  | +      route_config->method = it->second.string_value();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    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;
 | 
	
		
			
				|  |  |    }
 |