1 /** @file
2
3 Implementation of Parent Proxy routing
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 #include <atomic>
24 #include "HostStatus.h"
25 #include "ParentConsistentHash.h"
26
ParentConsistentHash(ParentRecord * parent_record)27 ParentConsistentHash::ParentConsistentHash(ParentRecord *parent_record)
28 {
29 int i;
30
31 ink_assert(parent_record->num_parents > 0);
32 parents[PRIMARY] = parent_record->parents;
33 parents[SECONDARY] = parent_record->secondary_parents;
34 ignore_query = parent_record->ignore_query;
35 secondary_mode = parent_record->secondary_mode;
36 ink_zero(foundParents);
37
38 chash[PRIMARY] = new ATSConsistentHash();
39
40 for (i = 0; i < parent_record->num_parents; i++) {
41 chash[PRIMARY]->insert(&(parent_record->parents[i]), parent_record->parents[i].weight, (ATSHash64 *)&hash[PRIMARY]);
42 }
43
44 if (parent_record->num_secondary_parents > 0) {
45 Debug("parent_select", "ParentConsistentHash(): initializing the secondary parents hash.");
46 chash[SECONDARY] = new ATSConsistentHash();
47
48 for (i = 0; i < parent_record->num_secondary_parents; i++) {
49 chash[SECONDARY]->insert(&(parent_record->secondary_parents[i]), parent_record->secondary_parents[i].weight,
50 (ATSHash64 *)&hash[SECONDARY]);
51 }
52 } else {
53 chash[SECONDARY] = nullptr;
54 }
55 Debug("parent_select", "Using a consistent hash parent selection strategy.");
56 }
57
~ParentConsistentHash()58 ParentConsistentHash::~ParentConsistentHash()
59 {
60 Debug("parent_select", "~ParentConsistentHash(): releasing hashes");
61 delete chash[PRIMARY];
62 delete chash[SECONDARY];
63 }
64
65 uint64_t
getPathHash(HttpRequestData * hrdata,ATSHash64 * h)66 ParentConsistentHash::getPathHash(HttpRequestData *hrdata, ATSHash64 *h)
67 {
68 const char *url_string_ref = nullptr;
69 int len;
70 URL *url = hrdata->hdr->url_get();
71
72 // Use over-ride URL from HttpTransact::State's cache_info.parent_selection_url, if present.
73 URL *ps_url = nullptr;
74 if (hrdata->cache_info_parent_selection_url) {
75 ps_url = *(hrdata->cache_info_parent_selection_url);
76 if (ps_url) {
77 url_string_ref = ps_url->string_get_ref(&len);
78 if (url_string_ref && len > 0) {
79 // Print the over-ride URL
80 Debug("parent_select", "Using Over-Ride String='%.*s'.", len, url_string_ref);
81 h->update(url_string_ref, len);
82 h->final();
83 return h->get();
84 }
85 }
86 }
87
88 // Always hash on '/' because paths returned by ATS are always stripped of it
89 h->update("/", 1);
90
91 url_string_ref = url->path_get(&len);
92 if (url_string_ref) {
93 h->update(url_string_ref, len);
94 }
95
96 if (!ignore_query) {
97 url_string_ref = url->query_get(&len);
98 if (url_string_ref) {
99 h->update("?", 1);
100 h->update(url_string_ref, len);
101 }
102 }
103
104 h->final();
105
106 return h->get();
107 }
108
109 // Helper function to abstract calling ATSConsistentHash lookup_by_hashval() vs lookup().
110 static pRecord *
chash_lookup(ATSConsistentHash * fhash,uint64_t path_hash,ATSConsistentHashIter * chashIter,bool * wrap_around,ATSHash64Sip24 * hash,bool * chash_init,bool * mapWrapped)111 chash_lookup(ATSConsistentHash *fhash, uint64_t path_hash, ATSConsistentHashIter *chashIter, bool *wrap_around,
112 ATSHash64Sip24 *hash, bool *chash_init, bool *mapWrapped)
113 {
114 pRecord *prtmp;
115
116 if (*chash_init == false) {
117 prtmp = (pRecord *)fhash->lookup_by_hashval(path_hash, chashIter, wrap_around);
118 *chash_init = true;
119 } else {
120 prtmp = (pRecord *)fhash->lookup(nullptr, chashIter, wrap_around, hash);
121 }
122 // Do not set wrap_around to true until we try all the parents at least once.
123 bool wrapped = *wrap_around;
124 *wrap_around = (*mapWrapped && *wrap_around) ? true : false;
125 if (!*mapWrapped && wrapped) {
126 *mapWrapped = true;
127 }
128 return prtmp;
129 }
130
131 void
selectParent(bool first_call,ParentResult * result,RequestData * rdata,unsigned int fail_threshold,unsigned int retry_time)132 ParentConsistentHash::selectParent(bool first_call, ParentResult *result, RequestData *rdata, unsigned int fail_threshold,
133 unsigned int retry_time)
134 {
135 ATSHash64Sip24 hash;
136 ATSConsistentHash *fhash;
137 HttpRequestData *request_info = static_cast<HttpRequestData *>(rdata);
138 bool firstCall = first_call;
139 bool parentRetry = false;
140 bool wrap_around[2] = {false, false};
141 int lookups = 0;
142 uint64_t path_hash = 0;
143 uint32_t last_lookup;
144 pRecord *prtmp = nullptr, *pRec = nullptr;
145 HostStatus &pStatus = HostStatus::instance();
146 HostStatus_t host_stat = HostStatus_t::HOST_STATUS_INIT;
147
148 Debug("parent_select", "ParentConsistentHash::%s(): Using a consistent hash parent selection strategy.", __func__);
149 ink_assert(numParents(result) > 0 || result->rec->go_direct == true);
150
151 // Should only get into this state if we are supposed to go direct.
152 if (parents[PRIMARY] == nullptr && parents[SECONDARY] == nullptr) {
153 if (result->rec->go_direct == true && result->rec->parent_is_proxy == true) {
154 result->result = PARENT_DIRECT;
155 } else {
156 result->result = PARENT_FAIL;
157 }
158 result->hostname = nullptr;
159 result->port = 0;
160 return;
161 }
162
163 // ----------------------------------------------------------------------------------------------------
164 // Initial parent look-up for either findParent() (firstCall) or nextParent() (!firstCall)
165 // ----------------------------------------------------------------------------------------------------
166
167 // firstCall means called from findParent() and always use PRIMARY parent list.
168 if (firstCall) {
169 last_lookup = PRIMARY;
170 } else {
171 // !firstCall means called from nextParent() and must determine which parent list to use.
172 switch (secondary_mode) {
173 case 2:
174 last_lookup = PRIMARY;
175 break;
176 case 3:
177 if (result->first_choice_status == HOST_STATUS_DOWN && chash[SECONDARY] != nullptr) {
178 last_lookup = SECONDARY;
179 } else {
180 last_lookup = PRIMARY;
181 }
182 break;
183 case 1:
184 default:
185 if (chash[SECONDARY] != nullptr) {
186 last_lookup = SECONDARY;
187 } else {
188 last_lookup = PRIMARY;
189 }
190 }
191 }
192
193 // Do the initial parent look-up.
194 path_hash = getPathHash(request_info, (ATSHash64 *)&hash);
195 fhash = chash[last_lookup];
196 do { // search until we've selected a different parent if !firstCall
197 prtmp = chash_lookup(fhash, path_hash, &result->chashIter[last_lookup], &wrap_around[last_lookup], &hash,
198 &result->chash_init[last_lookup], &result->mapWrapped[last_lookup]);
199 lookups++;
200 if (prtmp) {
201 pRec = (parents[last_lookup] + prtmp->idx);
202 } else {
203 pRec = nullptr;
204 }
205 if (firstCall) {
206 break;
207 }
208 } while (pRec && !firstCall && last_lookup == PRIMARY && strcmp(pRec->hostname, result->hostname) == 0);
209
210 Debug("parent_select", "Initial parent lookups: %d", lookups);
211
212 // ----------------------------------------------------------------------------------------------------
213 // Validate initial parent look-up and perform additional look-ups if required.
214 // ----------------------------------------------------------------------------------------------------
215
216 // didn't find a parent or the parent is marked unavailable or the parent is marked down
217 HostStatRec *hst = (pRec) ? pStatus.getHostStatus(pRec->hostname) : nullptr;
218 host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP;
219 if (firstCall) {
220 result->first_choice_status = host_stat;
221 }
222
223 // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason
224 // ignore the down status and mark it as available
225 if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) {
226 if (hst->reasons == Reason::SELF_DETECT) {
227 host_stat = HOST_STATUS_UP;
228 }
229 }
230 if (!pRec || (pRec && !pRec->available.load()) || host_stat == HOST_STATUS_DOWN) {
231 do {
232 // check if the host is retryable. It's retryable if the retry window has elapsed
233 // and the global host status is HOST_STATUS_UP
234 if (pRec && !pRec->available.load() && host_stat == HOST_STATUS_UP) {
235 Debug("parent_select", "Parent.failedAt = %u, retry = %u, xact_start = %u", static_cast<unsigned>(pRec->failedAt.load()),
236 static_cast<unsigned>(retry_time), static_cast<unsigned>(request_info->xact_start));
237 if ((pRec->failedAt.load() + retry_time) < request_info->xact_start) {
238 if (pRec->retriers.fetch_add(1, std::memory_order_relaxed) < max_retriers) {
239 parentRetry = true;
240 // make sure that the proper state is recorded in the result structure
241 result->last_parent = pRec->idx;
242 result->last_lookup = last_lookup;
243 result->retry = parentRetry;
244 result->result = PARENT_SPECIFIED;
245 Debug("parent_select", "Down parent %s is now retryable, retriers = %d, max_retriers = %d", pRec->hostname,
246 pRec->retriers.load(), max_retriers);
247 break;
248 } else {
249 pRec->retriers--;
250 }
251 }
252 }
253 Debug("parent_select", "wrap_around[PRIMARY]: %d, wrap_around[SECONDARY]: %d", wrap_around[PRIMARY], wrap_around[SECONDARY]);
254 if (!wrap_around[PRIMARY] || (chash[SECONDARY] != nullptr && !wrap_around[SECONDARY])) {
255 Debug("parent_select", "Selected parent %s is not available, looking up another parent.", pRec ? pRec->hostname : "[NULL]");
256 switch (secondary_mode) {
257 case 2:
258 if (!wrap_around[PRIMARY]) {
259 last_lookup = PRIMARY;
260 } else if (chash[SECONDARY] != nullptr && !wrap_around[SECONDARY]) {
261 last_lookup = SECONDARY;
262 }
263 break;
264 case 3:
265 if (result->first_choice_status == HOST_STATUS_DOWN) {
266 if (chash[SECONDARY] != nullptr && !wrap_around[SECONDARY]) {
267 last_lookup = SECONDARY;
268 } else if (!wrap_around[PRIMARY]) {
269 last_lookup = PRIMARY;
270 }
271 } else {
272 if (!wrap_around[PRIMARY]) {
273 last_lookup = PRIMARY;
274 } else if (chash[SECONDARY] != nullptr && !wrap_around[SECONDARY]) {
275 last_lookup = SECONDARY;
276 }
277 }
278 break;
279 case 1:
280 default:
281 if (chash[SECONDARY] != nullptr && !wrap_around[SECONDARY]) {
282 last_lookup = SECONDARY;
283 } else if (!wrap_around[PRIMARY]) {
284 last_lookup = PRIMARY;
285 }
286 }
287 fhash = chash[last_lookup];
288 prtmp = chash_lookup(fhash, path_hash, &result->chashIter[last_lookup], &wrap_around[last_lookup], &hash,
289 &result->chash_init[last_lookup], &result->mapWrapped[last_lookup]);
290 lookups++;
291 if (prtmp) {
292 pRec = (parents[last_lookup] + prtmp->idx);
293 Debug("parent_select", "Selected a new parent: %s.", pRec->hostname);
294 } else {
295 pRec = nullptr;
296 }
297 }
298 if (wrap_around[PRIMARY] && chash[SECONDARY] == nullptr) {
299 Debug("parent_select", "No available parents.");
300 break;
301 }
302 if (wrap_around[PRIMARY] && chash[SECONDARY] != nullptr && wrap_around[SECONDARY]) {
303 Debug("parent_select", "No available parents.");
304 break;
305 }
306 hst = (pRec) ? pStatus.getHostStatus(pRec->hostname) : nullptr;
307 host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP;
308 // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason
309 // ignore the down status and mark it as available
310 if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) {
311 if (hst->reasons == Reason::SELF_DETECT) {
312 host_stat = HOST_STATUS_UP;
313 }
314 }
315 } while (!pRec || !pRec->available.load() || host_stat == HOST_STATUS_DOWN);
316 }
317
318 Debug("parent_select", "Additional parent lookups: %d", lookups);
319
320 // ----------------------------------------------------------------------------------------------------
321 // Validate and return the final result.
322 // ----------------------------------------------------------------------------------------------------
323
324 // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason
325 // ignore the down status and mark it as available
326 if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) {
327 if (hst->reasons == Reason::SELF_DETECT) {
328 host_stat = HOST_STATUS_UP;
329 }
330 }
331 if (pRec && host_stat == HOST_STATUS_UP && (pRec->available.load() || result->retry)) {
332 result->result = PARENT_SPECIFIED;
333 result->hostname = pRec->hostname;
334 result->port = pRec->port;
335 result->last_parent = pRec->idx;
336 result->last_lookup = last_lookup;
337 result->retry = parentRetry;
338 ink_assert(result->hostname != nullptr);
339 ink_assert(result->port != 0);
340 Debug("parent_select", "Chosen parent: %s.%d", result->hostname, result->port);
341 } else {
342 if (result->rec->go_direct == true && result->rec->parent_is_proxy == true) {
343 result->result = PARENT_DIRECT;
344 } else {
345 result->result = PARENT_FAIL;
346 }
347 result->hostname = nullptr;
348 result->port = 0;
349 result->retry = false;
350 }
351
352 return;
353 }
354
355 uint32_t
numParents(ParentResult * result) const356 ParentConsistentHash::numParents(ParentResult *result) const
357 {
358 uint32_t n = 0;
359
360 switch (result->last_lookup) {
361 case PRIMARY:
362 n = result->rec->num_parents;
363 break;
364 case SECONDARY:
365 n = result->rec->num_secondary_parents;
366 break;
367 }
368
369 return n;
370 }
371
372 void
markParentUp(ParentResult * result)373 ParentConsistentHash::markParentUp(ParentResult *result)
374 {
375 pRecord *pRec;
376
377 // Make sure that we are being called back with with a
378 // result structure with a parent that is being retried
379 ink_release_assert(result->retry == true);
380 ink_assert(result->result == PARENT_SPECIFIED);
381 if (result->result != PARENT_SPECIFIED) {
382 return;
383 }
384 // If we were set through the API we currently have not failover
385 // so just return fail
386 if (result->is_api_result()) {
387 ink_assert(0);
388 return;
389 }
390
391 ink_assert((result->last_parent) < numParents(result));
392 pRec = parents[result->last_lookup] + result->last_parent;
393 pRec->available = true;
394 Debug("parent_select", "%s:%s(): marked %s:%d available.", __FILE__, __func__, pRec->hostname, pRec->port);
395
396 pRec->failedAt = static_cast<time_t>(0);
397 int old_count = pRec->failCount.exchange(0, std::memory_order_relaxed);
398 pRec->retriers = 0;
399
400 if (old_count > 0) {
401 Note("http parent proxy %s:%d restored", pRec->hostname, pRec->port);
402 }
403 }
404