1
#include "source/extensions/load_balancing_policies/subset/subset_lb_config.h"
2

            
3
#include "source/common/config/utility.h"
4
#include "source/common/upstream/upstream_impl.h"
5

            
6
namespace Envoy {
7
namespace Upstream {
8

            
9
SubsetSelector::SubsetSelector(const Protobuf::RepeatedPtrField<std::string>& selector_keys,
10
                               envoy::config::cluster::v3::Cluster::LbSubsetConfig::
11
                                   LbSubsetSelector::LbSubsetSelectorFallbackPolicy fallback_policy,
12
                               const Protobuf::RepeatedPtrField<std::string>& fallback_keys_subset,
13
                               bool single_host_per_subset)
14
194
    : selector_keys_(selector_keys.begin(), selector_keys.end()),
15
194
      fallback_keys_subset_(fallback_keys_subset.begin(), fallback_keys_subset.end()),
16
194
      fallback_policy_(fallback_policy), single_host_per_subset_(single_host_per_subset) {
17

            
18
194
  if (fallback_policy_ !=
19
194
      envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET) {
20
    // defining fallback_keys_subset_ for a fallback policy other than KEYS_SUBSET doesn't have
21
    // any effect and it is probably a user mistake. We should let the user know about it.
22
179
    if (!fallback_keys_subset_.empty()) {
23
1
      throw EnvoyException("fallback_keys_subset can be set only for KEYS_SUBSET fallback_policy");
24
1
    }
25
178
    return;
26
179
  }
27

            
28
  // if KEYS_SUBSET fallback policy is selected, fallback_keys_subset must not be empty, because
29
  // it would be the same as not defining fallback policy at all (global fallback policy would be
30
  // used)
31
15
  if (fallback_keys_subset_.empty()) {
32
1
    throw EnvoyException("fallback_keys_subset cannot be empty");
33
1
  }
34

            
35
  // We allow only for a fallback to a subset of the selector keys because this is probably the
36
  // only use case that makes sense (fallback from more specific selector to less specific
37
  // selector). Potentially we can relax this constraint in the future if there will be a use case
38
  // for this.
39
14
  if (!std::includes(selector_keys_.begin(), selector_keys_.end(), fallback_keys_subset_.begin(),
40
14
                     fallback_keys_subset_.end())) {
41
1
    throw EnvoyException("fallback_keys_subset must be a subset of selector keys");
42
1
  }
43

            
44
  // Enforce that the fallback_keys_subset_ set is smaller than the selector_keys_ set. Otherwise
45
  // we could end up with a infinite recursion of SubsetLoadBalancer::chooseHost().
46
13
  if (selector_keys_.size() == fallback_keys_subset_.size()) {
47
1
    throw EnvoyException("fallback_keys_subset cannot be equal to keys");
48
1
  }
49
13
}
50

            
51
SubsetLoadBalancerConfig::SubsetLoadBalancerConfig(
52
    Server::Configuration::ServerFactoryContext& factory_context, const SubsetLbConfigProto& config,
53
    absl::Status& creation_status)
54
3
    : subset_info_(std::make_unique<LoadBalancerSubsetInfoImpl>(config)) {
55
3
  absl::InlinedVector<absl::string_view, 4> missing_policies;
56

            
57
3
  for (const auto& policy : config.subset_lb_policy().policies()) {
58
3
    auto* factory = Config::Utility::getAndCheckFactory<Upstream::TypedLoadBalancerFactory>(
59
3
        policy.typed_extension_config(), /*is_optional=*/true);
60

            
61
3
    if (factory != nullptr) {
62
      // Load and validate the configuration.
63
2
      auto sub_lb_proto_message = factory->createEmptyConfigProto();
64
2
      THROW_IF_NOT_OK(Config::Utility::translateOpaqueConfig(
65
2
          policy.typed_extension_config().typed_config(),
66
2
          factory_context.messageValidationVisitor(), *sub_lb_proto_message));
67

            
68
2
      auto lb_config_or_error = factory->loadConfig(factory_context, *sub_lb_proto_message);
69
2
      SET_AND_RETURN_IF_NOT_OK(lb_config_or_error.status(), creation_status);
70
2
      child_lb_config_ = std::move(lb_config_or_error.value());
71
2
      child_lb_factory_ = factory;
72
2
      break;
73
2
    }
74

            
75
1
    missing_policies.push_back(policy.typed_extension_config().name());
76
1
  }
77

            
78
3
  if (child_lb_factory_ == nullptr) {
79
1
    creation_status = absl::InvalidArgumentError(
80
1
        fmt::format("cluster: didn't find a registered load balancer factory implementation for "
81
1
                    "subset lb with names from [{}]",
82
1
                    absl::StrJoin(missing_policies, ", ")));
83
1
  }
84
3
}
85

            
86
SubsetLoadBalancerConfig::SubsetLoadBalancerConfig(
87
    Server::Configuration::ServerFactoryContext& factory_context, const ClusterProto& cluster,
88
    absl::Status& creation_status)
89
51
    : subset_info_(std::make_unique<LoadBalancerSubsetInfoImpl>(cluster.lb_subset_config())) {
90
51
  ASSERT(subset_info_->isEnabled());
91

            
92
51
  auto sub_lb_pair = LegacyLbPolicyConfigHelper::getTypedLbConfigFromLegacyProtoWithoutSubset(
93
51
      factory_context, cluster);
94
51
  SET_AND_RETURN_IF_NOT_OK(sub_lb_pair.status(), creation_status);
95

            
96
51
  child_lb_factory_ = sub_lb_pair->factory;
97
51
  ASSERT(child_lb_factory_ != nullptr);
98
51
  child_lb_config_ = std::move(sub_lb_pair->config);
99
51
}
100

            
101
SubsetLoadBalancerConfig::SubsetLoadBalancerConfig(
102
    std::unique_ptr<LoadBalancerSubsetInfo> subset_info, TypedLoadBalancerFactory* child_factory,
103
    LoadBalancerConfigPtr child_config)
104
114
    : subset_info_(std::move(subset_info)), child_lb_factory_(child_factory),
105
114
      child_lb_config_(std::move(child_config)) {}
106

            
107
} // namespace Upstream
108
} // namespace Envoy