• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 12351442937

16 Dec 2024 11:13AM UTC coverage: 64.773% (+0.02%) from 64.751%
12351442937

Pull #14617

github

web-flow
Merge a5962f66f into 2c5c5b828
Pull Request #14617: rec: dedup records

37546 of 88848 branches covered (42.26%)

Branch coverage included in aggregate %.

77 of 77 new or added lines in 8 files covered. (100.0%)

42 existing lines in 12 files now uncovered.

125960 of 163583 relevant lines covered (77.0%)

4425415.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.6
/pdns/dnsdistdist/dnsdist-lbpolicies.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22

23
#include "dnsdist.hh"
24
#include "dnsdist-lbpolicies.hh"
25
#include "dnsdist-lua.hh"
26
#include "dnsdist-lua-ffi.hh"
27
#include "dolog.hh"
28
#include "dns_random.hh"
29

30
static constexpr size_t s_staticArrayCutOff = 16;
31
template <typename T> using DynamicIndexArray = std::vector<std::pair<T, size_t>>;
32
template <typename T> using StaticIndexArray = std::array<std::pair<T, size_t>, s_staticArrayCutOff>;
33

34
template <class T> static std::shared_ptr<DownstreamState> getLeastOutstanding(const ServerPolicy::NumberedServerVector& servers, T& poss)
35
{
28✔
36
  /* so you might wonder, why do we go through this trouble? The data on which we sort could change during the sort,
37
     which would suck royally and could even lead to crashes. So first we snapshot on what we sort, and then we sort */
38
  size_t usableServers = 0;
28✔
39
  for (const auto& d : servers) {
28!
40
    if (d.second->isUp()) {
14!
41
      poss.at(usableServers) = std::pair(std::tuple(d.second->outstanding.load(), d.second->d_config.order, d.second->getRelevantLatencyUsec()), d.first);
×
42
      usableServers++;
×
43
    }
×
44
  }
14✔
45

46
  if (usableServers == 0) {
28!
47
    return shared_ptr<DownstreamState>();
28✔
48
  }
28✔
49

50
  std::nth_element(poss.begin(), poss.begin(), poss.begin() + usableServers, [](const typename T::value_type& a, const typename T::value_type& b) { return a.first < b.first; });
×
51
  // minus 1 because the NumberedServerVector starts at 1 for Lua
52
  return servers.at(poss.begin()->second - 1).second;
×
53
}
28✔
54

55
// get server with least outstanding queries, and within those, with the lowest order, and within those: the fastest
56
shared_ptr<DownstreamState> leastOutstanding(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
57
{
3,213✔
58
  using LeastOutstandingType = std::tuple<int,int,double>;
3,213✔
59

60
  if (servers.size() == 1 && servers[0].second->isUp()) {
3,213✔
61
    return servers[0].second;
3,185✔
62
  }
3,185✔
63

64
  if (servers.size() <= s_staticArrayCutOff) {
28!
65
    StaticIndexArray<LeastOutstandingType> poss;
28✔
66
    return getLeastOutstanding(servers, poss);
28✔
67
  }
28✔
68

69
  DynamicIndexArray<LeastOutstandingType> poss;
×
70
  poss.resize(servers.size());
×
71
  return getLeastOutstanding(servers, poss);
×
72
}
28✔
73

74
shared_ptr<DownstreamState> firstAvailable(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
75
{
224✔
76
  for (auto& d : servers) {
224!
77
    if (d.second->isUp() && d.second->qps.checkOnly()) {
224!
78
      return d.second;
224✔
79
    }
224✔
80
  }
224✔
81
  return leastOutstanding(servers, dq);
×
82
}
224✔
83

84
template <class T> static std::shared_ptr<DownstreamState> getValRandom(const ServerPolicy::NumberedServerVector& servers, T& poss, const unsigned int val, const double targetLoad)
85
{
600✔
86
  constexpr int max = std::numeric_limits<int>::max();
600✔
87
  int sum = 0;
600✔
88

89
  size_t usableServers = 0;
600✔
90
  const auto weightedBalancingFactor = dnsdist::configuration::getImmutableConfiguration().d_weightedBalancingFactor;
600✔
91
  for (const auto& d : servers) {      // w=1, w=10 -> 1, 11
1,200!
92
    if (d.second->isUp() && (weightedBalancingFactor == 0 || (static_cast<double>(d.second->outstanding.load()) <= (targetLoad * d.second->d_config.d_weight)))) {
1,200!
93
      // Don't overflow sum when adding high weights
94
      if (d.second->d_config.d_weight > max - sum) {
1,200!
95
        sum = max;
200✔
96
      } else {
1,000✔
97
        sum += d.second->d_config.d_weight;
1,000✔
98
      }
1,000✔
99

100
      poss.at(usableServers) = std::pair(sum, d.first);
1,200✔
101
      usableServers++;
1,200✔
102
    }
1,200✔
103
  }
1,200✔
104

105
  // Catch the case where usableServers or sum are equal to 0 to avoid a SIGFPE
106
  if (usableServers == 0 || sum == 0) {
600!
107
    return shared_ptr<DownstreamState>();
×
108
  }
×
109

110
  int r = val % sum;
600✔
111
  auto p = std::upper_bound(poss.begin(), poss.begin() + usableServers, r, [](int r_, const typename T::value_type& a) { return  r_ < a.first;});
1,200✔
112
  if (p == poss.begin() + usableServers) {
600!
113
    return shared_ptr<DownstreamState>();
×
114
  }
×
115

116
  // minus 1 because the NumberedServerVector starts at 1 for Lua
117
  return servers.at(p->second - 1).second;
600✔
118
}
600✔
119

120
static shared_ptr<DownstreamState> valrandom(const unsigned int val, const ServerPolicy::NumberedServerVector& servers)
121
{
600✔
122
  using ValRandomType = int;
600✔
123
  double targetLoad = std::numeric_limits<double>::max();
600✔
124
  const auto weightedBalancingFactor = dnsdist::configuration::getImmutableConfiguration().d_weightedBalancingFactor;
600✔
125
  if (weightedBalancingFactor > 0) {
600!
126
    /* we start with one, representing the query we are currently handling */
127
    double currentLoad = 1;
×
128
    size_t totalWeight = 0;
×
129
    for (const auto& pair : servers) {
×
130
      if (pair.second->isUp()) {
×
131
        currentLoad += pair.second->outstanding;
×
132
        totalWeight += pair.second->d_config.d_weight;
×
133
      }
×
134
    }
×
135

136
    if (totalWeight > 0) {
×
137
      targetLoad = (currentLoad / static_cast<double>(totalWeight)) * weightedBalancingFactor;
×
138
    }
×
139
  }
×
140

141
  if (servers.size() <= s_staticArrayCutOff) {
600!
142
    StaticIndexArray<ValRandomType> poss;
600✔
143
    return getValRandom(servers, poss, val, targetLoad);
600✔
144
  }
600✔
145

146
  DynamicIndexArray<ValRandomType> poss;
×
147
  poss.resize(servers.size());
×
148
  return getValRandom(servers, poss, val, targetLoad);
×
149
}
600✔
150

151
shared_ptr<DownstreamState> wrandom(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
152
{
400✔
153
  return valrandom(dns_random_uint32(), servers);
400✔
154
}
400✔
155

156
shared_ptr<DownstreamState> whashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash)
157
{
200✔
158
  return valrandom(hash, servers);
200✔
159
}
200✔
160

161
shared_ptr<DownstreamState> whashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
162
{
200✔
163
  const auto hashPerturbation = dnsdist::configuration::getImmutableConfiguration().d_hashPerturbation;
200✔
164
  return whashedFromHash(servers, dq->ids.qname.hash(hashPerturbation));
200✔
165
}
200✔
166

167
shared_ptr<DownstreamState> chashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t qhash)
168
{
200✔
169
  unsigned int sel = std::numeric_limits<unsigned int>::max();
200✔
170
  unsigned int min = std::numeric_limits<unsigned int>::max();
200✔
171
  shared_ptr<DownstreamState> ret = nullptr, first = nullptr;
200✔
172

173
  double targetLoad = std::numeric_limits<double>::max();
200✔
174
  const auto consistentHashBalancingFactor = dnsdist::configuration::getImmutableConfiguration().d_consistentHashBalancingFactor;
200✔
175
  if (consistentHashBalancingFactor > 0) {
200!
176
    /* we start with one, representing the query we are currently handling */
177
    double currentLoad = 1;
×
178
    size_t totalWeight = 0;
×
179
    for (const auto& pair : servers) {
×
180
      if (pair.second->isUp()) {
×
181
        currentLoad += pair.second->outstanding;
×
182
        totalWeight += pair.second->d_config.d_weight;
×
183
      }
×
184
    }
×
185

186
    if (totalWeight > 0) {
×
187
      targetLoad = (currentLoad / static_cast<double>(totalWeight)) * consistentHashBalancingFactor;
×
188
    }
×
189
  }
×
190

191
  for (const auto& d: servers) {
400✔
192
    if (d.second->isUp() && (consistentHashBalancingFactor == 0 || static_cast<double>(d.second->outstanding.load()) <= (targetLoad * d.second->d_config.d_weight))) {
400!
193
      // make sure hashes have been computed
194
      if (!d.second->hashesComputed) {
400!
195
        d.second->hash();
×
196
      }
×
197
      {
400✔
198
        const auto& server = d.second;
400✔
199
        auto hashes = server->hashes.read_lock();
400✔
200
        // we want to keep track of the last hash
201
        if (min > *(hashes->begin())) {
400!
202
          min = *(hashes->begin());
400✔
203
          first = server;
400✔
204
        }
400✔
205

206
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
400✔
207
        if (hash_it != hashes->end()) {
400!
208
          if (*hash_it < sel) {
400✔
209
            sel = *hash_it;
297✔
210
            ret = server;
297✔
211
          }
297✔
212
        }
400✔
213
      }
400✔
214
    }
400✔
215
  }
400✔
216
  if (ret != nullptr) {
200!
217
    return ret;
200✔
218
  }
200✔
UNCOV
219
  if (first != nullptr) {
×
UNCOV
220
    return first;
×
UNCOV
221
  }
×
222
  return shared_ptr<DownstreamState>();
×
UNCOV
223
}
×
224

225
shared_ptr<DownstreamState> chashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
226
{
200✔
227
  const auto hashPerturbation = dnsdist::configuration::getImmutableConfiguration().d_hashPerturbation;
200✔
228
  return chashedFromHash(servers, dq->ids.qname.hash(hashPerturbation));
200✔
229
}
200✔
230

231
shared_ptr<DownstreamState> roundrobin(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
232
{
62✔
233
  if (servers.empty()) {
62!
234
    return shared_ptr<DownstreamState>();
×
235
  }
×
236

237
  vector<size_t> candidates;
62✔
238
  candidates.reserve(servers.size());
62✔
239

240
  for (auto& d : servers) {
124✔
241
    if (d.second->isUp()) {
124✔
242
      candidates.push_back(d.first);
100✔
243
    }
100✔
244
  }
124✔
245

246
  if (candidates.empty()) {
62✔
247
    if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_roundrobinFailOnNoServer) {
2!
248
      return shared_ptr<DownstreamState>();
2✔
249
    }
2✔
250
    for (auto& d : servers) {
×
251
      candidates.push_back(d.first);
×
252
    }
×
253
  }
×
254

255
  static unsigned int counter;
60✔
256
  return servers.at(candidates.at((counter++) % candidates.size()) - 1).second;
60✔
257
}
62✔
258

259
std::shared_ptr<const ServerPolicy::NumberedServerVector> getDownstreamCandidates(const std::string& poolName)
260
{
×
261
  std::shared_ptr<ServerPool> pool = getPool(poolName);
×
262
  return pool->getServers();
×
263
}
×
264

265
std::shared_ptr<ServerPool> createPoolIfNotExists(const string& poolName)
266
{
1,201✔
267
  {
1,201✔
268
    const auto& pools = dnsdist::configuration::getCurrentRuntimeConfiguration().d_pools;
1,201✔
269
    const auto poolIt = pools.find(poolName);
1,201✔
270
    if (poolIt != pools.end()) {
1,201✔
271
      return poolIt->second;
504✔
272
    }
504✔
273
  }
1,201✔
274

275
  if (!poolName.empty()) {
697✔
276
    vinfolog("Creating pool %s", poolName);
63✔
277
  }
63✔
278

279
  auto pool = std::make_shared<ServerPool>();
697✔
280
  dnsdist::configuration::updateRuntimeConfiguration([&poolName,&pool](dnsdist::configuration::RuntimeConfiguration& config) {
697✔
281
    config.d_pools.emplace(poolName, pool);
697✔
282
  });
697✔
283

284
  return pool;
697✔
285
}
1,201✔
286

287
void setPoolPolicy(const string& poolName, std::shared_ptr<ServerPolicy> policy)
288
{
2✔
289
  std::shared_ptr<ServerPool> pool = createPoolIfNotExists(poolName);
2✔
290
  if (!poolName.empty()) {
2!
291
    vinfolog("Setting pool %s server selection policy to %s", poolName, policy->getName());
×
292
  } else {
2✔
293
    vinfolog("Setting default pool server selection policy to %s", policy->getName());
2!
294
  }
2✔
295
  pool->policy = std::move(policy);
2✔
296
}
2✔
297

298
void addServerToPool(const string& poolName, std::shared_ptr<DownstreamState> server)
299
{
748✔
300
  std::shared_ptr<ServerPool> pool = createPoolIfNotExists(poolName);
748✔
301
  if (!poolName.empty()) {
748✔
302
    vinfolog("Adding server to pool %s", poolName);
60✔
303
  } else {
692✔
304
    vinfolog("Adding server to default pool");
688✔
305
  }
688✔
306
  pool->addServer(server);
748✔
307
}
748✔
308

309
void removeServerFromPool(const string& poolName, std::shared_ptr<DownstreamState> server)
310
{
3✔
311
  std::shared_ptr<ServerPool> pool = getPool(poolName);
3✔
312

313
  if (!poolName.empty()) {
3✔
314
    vinfolog("Removing server from pool %s", poolName);
1!
315
  }
1✔
316
  else {
2✔
317
    vinfolog("Removing server from default pool");
2!
318
  }
2✔
319

320
  pool->removeServer(server);
3✔
321
}
3✔
322

323
std::shared_ptr<ServerPool> getPool(const std::string& poolName)
324
{
4,357✔
325
  const auto& pools = dnsdist::configuration::getCurrentRuntimeConfiguration().d_pools;
4,357✔
326
  auto poolIt = pools.find(poolName);
4,357✔
327
  if (poolIt == pools.end()) {
4,357✔
328
    throw std::out_of_range("No pool named " + poolName);
3✔
329
  }
3✔
330

331
  return poolIt->second;
4,354✔
332
}
4,357✔
333

334
ServerPolicy::ServerPolicy(const std::string& name_, const std::string& code): d_name(name_), d_perThreadPolicyCode(code), d_isLua(true), d_isFFI(true), d_isPerThread(true)
335
{
2✔
336
  LuaContext tmpContext;
2✔
337
  setupLuaLoadBalancingContext(tmpContext);
2✔
338
  auto ret = tmpContext.executeCode<ServerPolicy::ffipolicyfunc_t>(code);
2✔
339
}
2✔
340

341
struct ServerPolicy::PerThreadState
342
{
343
  LuaContext d_luaContext;
344
  std::unordered_map<std::string, ffipolicyfunc_t> d_policies;
345
};
346

347
thread_local std::unique_ptr<ServerPolicy::PerThreadState> ServerPolicy::t_perThreadState;
348

349
const ServerPolicy::ffipolicyfunc_t& ServerPolicy::getPerThreadPolicy() const
350
{
20✔
351
  auto& state = t_perThreadState;
20✔
352
  if (!state) {
20✔
353
    state = std::make_unique<ServerPolicy::PerThreadState>();
2✔
354
    setupLuaLoadBalancingContext(state->d_luaContext);
2✔
355
  }
2✔
356

357
  const auto& policyIt = state->d_policies.find(d_name);
20✔
358
  if (policyIt != state->d_policies.end()) {
20✔
359
    return policyIt->second;
18✔
360
  }
18✔
361

362
  auto newPolicy = state->d_luaContext.executeCode<ServerPolicy::ffipolicyfunc_t>(d_perThreadPolicyCode);
2✔
363
  state->d_policies[d_name] = std::move(newPolicy);
2✔
364
  return state->d_policies.at(d_name);
2✔
365
}
20✔
366

367
std::shared_ptr<DownstreamState> ServerPolicy::getSelectedBackend(const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dq) const
368
{
6,341✔
369
  std::shared_ptr<DownstreamState> selectedBackend{nullptr};
6,341✔
370

371
  if (d_isLua) {
6,341✔
372
    if (!d_isFFI) {
2,042✔
373
      auto lock = g_lua.lock();
2,020✔
374
      selectedBackend = d_policy(servers, &dq);
2,020✔
375
    }
2,020✔
376
    else {
22✔
377
      dnsdist_ffi_dnsquestion_t dnsq(&dq);
22✔
378
      dnsdist_ffi_servers_list_t serversList(servers);
22✔
379
      unsigned int selected = 0;
22✔
380

381
      if (!d_isPerThread) {
22✔
382
        auto lock = g_lua.lock();
2✔
383
        selected = d_ffipolicy(&serversList, &dnsq);
2✔
384
      }
2✔
385
      else {
20✔
386
        const auto& policy = getPerThreadPolicy();
20✔
387
        selected = policy(&serversList, &dnsq);
20✔
388
      }
20✔
389

390
      if (selected >= servers.size()) {
22✔
391
        /* invalid offset, meaning that there is no server available */
392
        return {};
2✔
393
      }
2✔
394

395
      selectedBackend = servers.at(selected).second;
20✔
396
    }
20✔
397
  }
2,042✔
398
  else {
4,299✔
399
    selectedBackend = d_policy(servers, &dq);
4,299✔
400
  }
4,299✔
401

402
  return selectedBackend;
6,339✔
403
}
6,341✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc