1
#include "source/extensions/load_balancing_policies/dynamic_modules/config.h"
2

            
3
#include "envoy/server/factory_context.h"
4

            
5
#include "source/common/protobuf/utility.h"
6
#include "source/common/runtime/runtime_features.h"
7
#include "source/extensions/dynamic_modules/dynamic_modules.h"
8
#include "source/extensions/load_balancing_policies/dynamic_modules/load_balancer.h"
9

            
10
namespace Envoy {
11
namespace Extensions {
12
namespace LoadBalancingPolicies {
13
namespace DynamicModules {
14

            
15
namespace {
16

            
17
/**
18
 * Thread-aware load balancer implementation that creates DynamicModuleLoadBalancer instances.
19
 */
20
class ThreadAwareLb : public Upstream::ThreadAwareLoadBalancer {
21
public:
22
31
  ThreadAwareLb(Upstream::LoadBalancerFactorySharedPtr factory) : factory_(std::move(factory)) {}
23

            
24
31
  Upstream::LoadBalancerFactorySharedPtr factory() override { return factory_; }
25
31
  absl::Status initialize() override { return absl::OkStatus(); }
26

            
27
private:
28
  Upstream::LoadBalancerFactorySharedPtr factory_;
29
};
30

            
31
/**
32
 * Factory for creating worker-local DynamicModuleLoadBalancer instances.
33
 */
34
class LbFactory : public Upstream::LoadBalancerFactory {
35
public:
36
  LbFactory(DynamicModuleLbConfigSharedPtr config, const std::string& cluster_name)
37
31
      : config_(std::move(config)), cluster_name_(cluster_name) {}
38

            
39
30
  Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams params) override {
40
30
    return std::make_unique<DynamicModuleLoadBalancer>(config_, params.priority_set, cluster_name_);
41
30
  }
42

            
43
  bool recreateOnHostChange() const override { return false; }
44

            
45
private:
46
  DynamicModuleLbConfigSharedPtr config_;
47
  const std::string cluster_name_;
48
};
49

            
50
} // namespace
51

            
52
Upstream::ThreadAwareLoadBalancerPtr
53
Factory::create(OptRef<const Upstream::LoadBalancerConfig> lb_config,
54
                const Upstream::ClusterInfo& cluster_info,
55
                const Upstream::PrioritySet& /*priority_set*/, Runtime::Loader&,
56
31
                Random::RandomGenerator& /*random*/, TimeSource& /*time_source*/) {
57
31
  const auto* typed_config = dynamic_cast<const TypedDynamicModuleLbConfig*>(lb_config.ptr());
58
31
  ASSERT(typed_config != nullptr, "Invalid dynamic module load balancer config");
59

            
60
31
  return std::make_unique<ThreadAwareLb>(
61
31
      std::make_shared<LbFactory>(typed_config->config(), cluster_info.name()));
62
31
}
63

            
64
absl::StatusOr<Upstream::LoadBalancerConfigPtr>
65
Factory::loadConfig(Server::Configuration::ServerFactoryContext& context,
66
49
                    const Protobuf::Message& config) {
67
49
  const auto& typed_config = dynamic_cast<const DynamicModulesLbProto&>(config);
68
49
  const auto& module_config = typed_config.dynamic_module_config();
69
49
  const std::string& module_name = module_config.name();
70

            
71
  // Load the dynamic module.
72
49
  auto module_or_error = Envoy::Extensions::DynamicModules::newDynamicModuleByName(
73
49
      module_name, module_config.do_not_close(), module_config.load_globally());
74
49
  if (!module_or_error.ok()) {
75
1
    return absl::InvalidArgumentError(fmt::format("failed to load dynamic module '{}': {}",
76
1
                                                  module_name, module_or_error.status().message()));
77
1
  }
78

            
79
  // Use configured metrics namespace or fall back to the default.
80
48
  const std::string metrics_namespace = module_config.metrics_namespace().empty()
81
48
                                            ? std::string(DefaultMetricsNamespace)
82
48
                                            : module_config.metrics_namespace();
83

            
84
  // Create the load balancer configuration.
85
48
  std::string config_bytes;
86
48
  if (typed_config.has_lb_policy_config()) {
87
3
    auto config_or_error = MessageUtil::knownAnyToBytes(typed_config.lb_policy_config());
88
3
    RETURN_IF_NOT_OK_REF(config_or_error.status());
89
3
    config_bytes = std::move(config_or_error.value());
90
3
  }
91
48
  auto lb_config_or_error =
92
48
      DynamicModuleLbConfig::create(typed_config.lb_policy_name(), config_bytes, metrics_namespace,
93
48
                                    std::move(module_or_error.value()), context.serverScope());
94
48
  if (!lb_config_or_error.ok()) {
95
2
    return absl::InvalidArgumentError(
96
2
        fmt::format("failed to create load balancer config for module '{}': {}", module_name,
97
2
                    lb_config_or_error.status().message()));
98
2
  }
99

            
100
  // When the runtime guard is enabled, register the metrics namespace as a custom stat namespace.
101
  // This causes the namespace prefix to be stripped from prometheus output and no envoy_ prefix
102
  // is added. This is the legacy behavior for backward compatibility.
103
46
  if (Runtime::runtimeFeatureEnabled(
104
46
          "envoy.reloadable_features.dynamic_modules_strip_custom_stat_prefix")) {
105
    context.api().customStatNamespaces().registerStatNamespace(metrics_namespace);
106
  }
107

            
108
46
  return std::make_unique<TypedDynamicModuleLbConfig>(std::move(lb_config_or_error.value()));
109
48
}
110

            
111
REGISTER_FACTORY(Factory, Upstream::TypedLoadBalancerFactory);
112

            
113
} // namespace DynamicModules
114
} // namespace LoadBalancingPolicies
115
} // namespace Extensions
116
} // namespace Envoy