Coverage Report

Created: 2026-10-23 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/trafficserver/include/proxy/ParentSelection.h
Line
Count
Source
1
/** @file
2
3
  A brief file description
4
5
  @section license License
6
7
  Licensed to the Apache Software Foundation (ASF) under one
8
  or more contributor license agreements.  See the NOTICE file
9
  distributed with this work for additional information
10
  regarding copyright ownership.  The ASF licenses this file
11
  to you under the Apache License, Version 2.0 (the
12
  "License"); you may not use this file except in compliance
13
  with the License.  You may obtain a copy of the License at
14
15
      http://www.apache.org/licenses/LICENSE-2.0
16
17
  Unless required by applicable law or agreed to in writing, software
18
  distributed under the License is distributed on an "AS IS" BASIS,
19
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
  See the License for the specific language governing permissions and
21
  limitations under the License.
22
 */
23
24
/*****************************************************************************
25
 *
26
 *  ParentSelection.h - Interface to Parent Selection System
27
 *
28
 *
29
 ****************************************************************************/
30
31
#pragma once
32
33
#include "iocore/eventsystem/ConfigProcessor.h"
34
#include "proxy/ControlBase.h"
35
#include "proxy/ControlMatcher.h"
36
#include "records/RecProcess.h"
37
#include "tscore/ConsistentHash.h"
38
#include "tscore/Hash.h"
39
#include "tscore/Tokenizer.h"
40
#include "tscore/ink_apidefs.h"
41
#include "proxy/HostStatus.h"
42
43
#include <algorithm>
44
#include <memory>
45
#include <vector>
46
47
#define MAX_PARENTS           64
48
0
#define DEFAULT_PARENT_WEIGHT 1.0
49
50
struct RequestData;
51
struct matcher_line;
52
struct ParentResult;
53
struct OverridableHttpConfigParams;
54
class ParentRecord;
55
class ParentSelectionStrategy;
56
57
enum class ParentResultType {
58
  UNDEFINED,
59
  DIRECT,
60
  SPECIFIED,
61
  AGENT,
62
  FAIL,
63
};
64
65
static const char *ParentResultStr[] = {"ParentResultType::UNDEFINED", "ParentResultType::DIRECT", "ParentResultType::SPECIFIED",
66
                                        "ParentResultType::AGENT", "ParentResultType::FAIL"};
67
68
enum class ParentRR_t { NO_ROUND_ROBIN = 0, STRICT_ROUND_ROBIN, HASH_ROUND_ROBIN, CONSISTENT_HASH, LATCHED_ROUND_ROBIN, UNDEFINED };
69
70
enum class ParentRetry_t {
71
  NONE               = 0,
72
  SIMPLE             = 1,
73
  UNAVAILABLE_SERVER = 2,
74
  // both simple and unavailable server retry
75
  BOTH = 3
76
};
77
78
enum class ParentHashAlgorithm { SIPHASH24 = 0, SIPHASH13 };
79
80
struct UnavailableServerResponseCodes {
81
  UnavailableServerResponseCodes(char *val);
82
0
  ~UnavailableServerResponseCodes(){};
83
84
  bool
85
  contains(int code)
86
0
  {
87
0
    return binary_search(codes.begin(), codes.end(), code);
88
0
  }
89
90
private:
91
  std::vector<int> codes;
92
};
93
94
struct SimpleRetryResponseCodes {
95
  SimpleRetryResponseCodes(char *val);
96
0
  ~SimpleRetryResponseCodes(){};
97
98
  bool
99
  contains(int code)
100
0
  {
101
0
    return binary_search(codes.begin(), codes.end(), code);
102
0
  }
103
104
private:
105
  std::vector<int> codes;
106
};
107
108
// struct pRecord
109
//
110
//    A record for an individual parent
111
//
112
struct pRecord : ATSConsistentHashNode {
113
public:
114
  char                hostname[MAXDNAME + 1];
115
  int                 port;
116
  std::atomic<time_t> failedAt  = 0;
117
  std::atomic<int>    failCount = 0;
118
  int32_t             upAt;
119
  const char         *scheme; // for which parent matches (if any)
120
  int                 idx;
121
  float               weight;
122
  char                hash_string[MAXDNAME + 1];
123
};
124
125
using P_table = ControlMatcher<ParentRecord, ParentResult>;
126
127
// class ParentRecord : public ControlBase
128
//
129
//   A record for a configuration line in the parent.config
130
//    file
131
//
132
class ParentRecord : public ControlBase
133
{
134
public:
135
  ~ParentRecord();
136
137
  Result Init(matcher_line *line_info);
138
  bool   DefaultInit(char *val);
139
  void   UpdateMatch(ParentResult *result, RequestData *rdata);
140
141
  void Print() const;
142
143
  pRecord *parents               = nullptr;
144
  pRecord *secondary_parents     = nullptr;
145
  int      num_parents           = 0;
146
  int      num_secondary_parents = 0;
147
148
  bool
149
  bypass_ok() const
150
0
  {
151
0
    return go_direct;
152
0
  }
153
154
  const char *scheme = nullptr;
155
  // private:
156
  void                            PreProcessParents(const char *val, const int line_num, char *buf, size_t len);
157
  const char                     *ProcessParents(char *val, bool isPrimary);
158
  bool                            ignore_query                       = false;
159
  uint32_t                        rr_next                            = 0;
160
  bool                            go_direct                          = true;
161
  bool                            parent_is_proxy                    = true;
162
  ParentSelectionStrategy        *selection_strategy                 = nullptr;
163
  UnavailableServerResponseCodes *unavailable_server_retry_responses = nullptr;
164
  SimpleRetryResponseCodes       *simple_server_retry_responses      = nullptr;
165
  ParentRetry_t                   parent_retry                       = ParentRetry_t::NONE;
166
  int                             max_simple_retries                 = 1;
167
  int                             max_unavailable_server_retries     = 1;
168
  int                             secondary_mode                     = 1;
169
  bool                            ignore_self_detect                 = false;
170
  bool                            host_override                      = false;
171
  ParentHashAlgorithm             consistent_hash_algorithm          = ParentHashAlgorithm::SIPHASH24;
172
  uint64_t                        consistent_hash_seed0              = 0;
173
  uint64_t                        consistent_hash_seed1              = 0;
174
  int consistent_hash_replicas = 1024; // Number of virtual nodes per host (int to match ATSConsistentHash constructor)
175
};
176
177
// If the parent was set by the external customer api,
178
//   our HttpRequestData structure told us what parent to
179
//   use and we are only called to preserve clean interface
180
//   between HttpTransact & the parent selection code.  The following
181
ParentRecord *const extApiRecord = (ParentRecord *)0xeeeeffff;
182
183
// used here to set the number of ATSConsistentHashIter's
184
// used in NextHopSelectionStrategy to limit the host group
185
// size as well, group size is one to one with the number of rings
186
constexpr const uint32_t MAX_GROUP_RINGS = 5;
187
188
struct ParentResult {
189
0
  ParentResult() { reset(); }
190
  // For outside consumption
191
  ParentResultType result;
192
  const char      *hostname;
193
  const char      *url;
194
  int              port;
195
  bool             retry;
196
  bool             chash_init[MAX_GROUP_RINGS];
197
  bool             use_pristine;
198
  TSHostStatus     first_choice_status;
199
  bool             do_not_cache_response;
200
201
  void
202
  reset()
203
0
  {
204
    // Public members
205
0
    result                = ParentResultType::UNDEFINED;
206
0
    hostname              = nullptr;
207
0
    url                   = nullptr;
208
0
    port                  = 0;
209
0
    retry                 = false;
210
0
    use_pristine          = false;
211
0
    first_choice_status   = TSHostStatus::TS_HOST_STATUS_INIT;
212
0
    do_not_cache_response = false;
213
214
    // Private members
215
0
    line_number  = -1;
216
0
    rec          = nullptr;
217
0
    last_parent  = 0;
218
0
    start_parent = 0;
219
0
    last_group   = 0;
220
0
    wrap_around  = false;
221
0
    last_lookup  = 0;
222
223
0
    for (uint32_t i = 0; i < MAX_GROUP_RINGS; ++i) {
224
0
      chash_init[i] = false;
225
0
      mapWrapped[i] = false;
226
0
      chashIter[i]  = ATSConsistentHashIter{};
227
0
    }
228
0
  }
229
230
  bool
231
  is_api_result() const
232
0
  {
233
0
    return rec == extApiRecord;
234
0
  }
235
236
  // Do we have some result?
237
  bool
238
  is_some() const
239
0
  {
240
0
    if (rec == nullptr) {
241
      // If we don't have a result, we either haven't done a parent
242
      // lookup yet (ParentResultType::UNDEFINED), or the lookup didn't match
243
      // anything (ParentResultType::DIRECT).
244
0
      ink_assert(result == ParentResultType::UNDEFINED || result == ParentResultType::DIRECT);
245
0
      return false;
246
0
    }
247
248
0
    return true;
249
0
  }
250
251
  bool
252
  parent_is_proxy() const
253
0
  {
254
    // Parents set by the TSHttpTxnParentProxySet API are always considered proxies rather than origins.
255
0
    return is_api_result() ? true : rec->parent_is_proxy;
256
0
  }
257
258
  ParentRetry_t
259
  retry_type() const
260
0
  {
261
0
    return is_api_result() ? ParentRetry_t::NONE : rec->parent_retry;
262
0
  }
263
264
  bool
265
  host_override() const
266
0
  {
267
0
    if (is_api_result()) {
268
0
      return false;
269
0
    }
270
271
0
    if (!is_some()) {
272
0
      return false;
273
0
    }
274
275
0
    return rec->host_override;
276
0
  }
277
278
  unsigned
279
  max_retries(ParentRetry_t method) const
280
0
  {
281
    // There's no API for specifying the retries, so you get 0.
282
0
    if (is_api_result()) {
283
0
      return 0;
284
0
    }
285
286
0
    switch (method) {
287
0
    case ParentRetry_t::NONE:
288
0
      return 0;
289
0
    case ParentRetry_t::SIMPLE:
290
0
      return rec->max_simple_retries;
291
0
    case ParentRetry_t::UNAVAILABLE_SERVER:
292
0
      return rec->max_unavailable_server_retries;
293
0
    case ParentRetry_t::BOTH:
294
0
      return std::max(rec->max_unavailable_server_retries, rec->max_simple_retries);
295
0
    }
296
297
0
    return 0;
298
0
  }
299
300
  bool
301
  response_is_retryable(ParentRetry_t retry_type, HTTPStatus response_code) const
302
0
  {
303
0
    Dbg(dbg_ctl_parent_select, "In response_is_retryable, code: %d, type: %d", static_cast<int>(response_code),
304
0
        static_cast<int>(retry_type));
305
0
    if (retry_type == ParentRetry_t::BOTH) {
306
0
      Dbg(dbg_ctl_parent_select, "Saw retry both");
307
0
      return (rec->unavailable_server_retry_responses->contains(static_cast<int>(response_code)) ||
308
0
              rec->simple_server_retry_responses->contains(static_cast<int>(response_code)));
309
0
    } else if (retry_type == ParentRetry_t::UNAVAILABLE_SERVER) {
310
0
      Dbg(dbg_ctl_parent_select, "Saw retry unavailable server");
311
0
      return rec->unavailable_server_retry_responses->contains(static_cast<int>(response_code));
312
0
    } else if (retry_type == ParentRetry_t::SIMPLE) {
313
0
      Dbg(dbg_ctl_parent_select, "Saw retry simple retry");
314
0
      return rec->simple_server_retry_responses->contains(static_cast<int>(response_code));
315
0
    } else {
316
0
      return false;
317
0
    }
318
0
  }
319
320
  bool
321
  bypass_ok() const
322
0
  {
323
0
    if (is_api_result()) {
324
0
      return false;
325
0
    } else {
326
      // Caller should check for a valid result beforehand.
327
0
      ink_assert(result != ParentResultType::UNDEFINED);
328
0
      ink_assert(is_some());
329
0
      return rec->bypass_ok();
330
0
    }
331
0
  }
332
333
  void
334
  print()
335
0
  {
336
0
    printf("ParentResult - hostname: %s, port: %d, retry: %s, line_number: %d, last_parent: %d, start_parent: %d, wrap_around: %s, "
337
0
           "last_lookup: %d, result: %s\n",
338
0
           hostname, port, (retry) ? "true" : "false", line_number, last_parent, start_parent, (wrap_around) ? "true" : "false",
339
0
           last_lookup, ParentResultStr[static_cast<int>(result)]);
340
0
  }
341
342
  static DbgCtl dbg_ctl_parent_select;
343
344
private:
345
  // Internal use only
346
  //   Not to be modified by HTTP
347
  int           line_number;
348
  ParentRecord *rec;
349
  uint32_t      last_parent;
350
  uint32_t      start_parent;
351
  uint32_t      last_group;
352
  bool          wrap_around;
353
  bool          mapWrapped[MAX_GROUP_RINGS];
354
  // state for consistent hash.
355
  int                   last_lookup;
356
  ATSConsistentHashIter chashIter[MAX_GROUP_RINGS];
357
358
  friend class NextHopSelectionStrategy;
359
  friend class NextHopRoundRobin;
360
  friend class NextHopConsistentHash;
361
  friend class ParentConsistentHash;
362
  friend class ParentRoundRobin;
363
  friend class ParentConfigParams;
364
  friend class ParentRecord;
365
  friend class ParentSelectionStrategy;
366
};
367
368
struct ParentSelectionPolicy {
369
  int32_t ParentRetryTime = 0;
370
  int32_t ParentEnable    = 0;
371
  int32_t FailThreshold   = 0;
372
  ParentSelectionPolicy();
373
};
374
375
//
376
// API definition.
377
class ParentSelectionStrategy
378
{
379
public:
380
  int max_retriers = 0;
381
382
0
  ParentSelectionStrategy() { max_retriers = RecGetRecordInt("proxy.config.http.parent_proxy.max_trans_retries").value_or(0); }
383
  //
384
  // Return the pRecord.
385
  virtual pRecord *getParents(ParentResult *result) = 0;
386
  // void selectParent(bool firstCall, ParentResult *result, RequestData *rdata, unsigned int fail_threshold, unsigned int
387
  // retry_time)
388
  //
389
  // The implementation parent lookup.
390
  //
391
  virtual void selectParent(bool firstCall, ParentResult *result, RequestData *rdata, unsigned int fail_threshold,
392
                            unsigned int retry_time) = 0;
393
394
  // uint32_t numParents(ParentResult *result);
395
  //
396
  // Returns the number of parent records in a strategy.
397
  //
398
  virtual uint32_t numParents(ParentResult *result) const = 0;
399
  void             markParentDown(ParentResult *result, unsigned int fail_threshold, unsigned int retry_time);
400
  void             markParentUp(ParentResult *result);
401
402
  // virtual destructor.
403
0
  virtual ~ParentSelectionStrategy(){};
404
};
405
406
class ParentConfigParams : public ConfigInfo
407
{
408
public:
409
  explicit ParentConfigParams(P_table *_parent_table);
410
  ~ParentConfigParams() override;
411
412
  bool apiParentExists(HttpRequestData *rdata);
413
  void findParent(HttpRequestData *rdata, ParentResult *result, unsigned int fail_threshold, unsigned int retry_time);
414
  void nextParent(HttpRequestData *rdata, ParentResult *result, unsigned int fail_threshold, unsigned int retry_time);
415
  bool parentExists(HttpRequestData *rdata);
416
417
  // implementation of functions from ParentSelectionStrategy.
418
  void
419
  selectParent(bool firstCall, ParentResult *result, RequestData *rdata, unsigned int fail_threshold, unsigned int retry_time)
420
0
  {
421
0
    if (!result->is_api_result()) {
422
0
      ink_release_assert(result->rec->selection_strategy != nullptr);
423
0
      return result->rec->selection_strategy->selectParent(firstCall, result, rdata, fail_threshold, retry_time);
424
0
    }
425
0
  }
426
427
  void
428
  markParentDown(ParentResult *result, unsigned int fail_threshold, unsigned int retry_time)
429
0
  {
430
0
    if (!result->is_api_result()) {
431
0
      ink_release_assert(result->rec->selection_strategy != nullptr);
432
0
      result->rec->selection_strategy->markParentDown(result, fail_threshold, retry_time);
433
0
    }
434
0
  }
435
436
  void
437
  markParentUp(ParentResult *result)
438
0
  {
439
0
    if (!result->is_api_result()) {
440
0
      ink_release_assert(result != nullptr);
441
0
      result->rec->selection_strategy->markParentUp(result);
442
0
    }
443
0
  }
444
445
  uint32_t
446
  numParents(ParentResult *result)
447
0
  {
448
0
    if (result->is_api_result()) {
449
0
      return 1;
450
0
    } else {
451
0
      ink_release_assert(result->rec->selection_strategy != nullptr);
452
0
      return result->rec->selection_strategy->numParents(result);
453
0
    }
454
0
  }
455
456
  P_table              *parent_table;
457
  ParentRecord         *DefaultParent;
458
  ParentSelectionPolicy policy;
459
};
460
461
class HttpRequestData;
462
463
struct ParentConfig {
464
public:
465
  static void startup();
466
  static void reconfigure(ConfigContext ctx = {});
467
  static void print();
468
  static void set_parent_table(P_table *pTable, ParentRecord *rec, int num_elements);
469
470
  static ParentConfigParams *
471
  acquire()
472
0
  {
473
0
    return (ParentConfigParams *)configProcessor.get(ParentConfig::m_id);
474
0
  }
475
476
  static void
477
  release(ParentConfigParams *strategy)
478
0
  {
479
0
    configProcessor.release(ParentConfig::m_id, strategy);
480
0
  }
481
482
  static int m_id;
483
};
484
485
// Helper Functions
486
ParentRecord *createDefaultParent(char *val);
487
488
// Hash utility functions
489
ParentHashAlgorithm        parseHashAlgorithm(std::string_view name);
490
std::unique_ptr<ATSHash64> createHashInstance(ParentHashAlgorithm algo, uint64_t seed0, uint64_t seed1);
491
492
// Unit Test Functions
493
void show_result(ParentResult *aParentResult);
494
void br(HttpRequestData *h, const char *os_hostname, sockaddr const *dest_ip = nullptr); // short for build request
495
int  verify(ParentResult *r, ParentResultType e, const char *h, int p);
496
497
/*
498
  For supporting multiple Socks servers, we essentially use the
499
  ParentSelection infrastructure. Only the initialization is different.
500
  If needed, we will have to implement most of the functions in
501
  ParentSection.cc for Socks as well. For right now we will just use
502
  ParentSelection
503
504
  All the members in ParentConfig are static. Right now
505
  we will duplicate the code for these static functions.
506
*/
507
struct SocksServerConfig {
508
  static void startup();
509
  static void reconfigure();
510
  static void print();
511
512
  static ParentConfigParams *
513
  acquire()
514
0
  {
515
0
    return (ParentConfigParams *)configProcessor.get(SocksServerConfig::m_id);
516
0
  }
517
  static void
518
  release(ParentConfigParams *params)
519
0
  {
520
0
    configProcessor.release(SocksServerConfig::m_id, params);
521
0
  }
522
523
  static int m_id;
524
};