blob: 9d5546e2e4b8ddbd31acf62d88e2d647d74a2510 [file] [log] [blame]
/** @file
Implementation of Parent Proxy routing
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "P_EventSystem.h"
#include "ParentSelection.h"
#include "ParentConsistentHash.h"
#include "ParentRoundRobin.h"
#include "ControlMatcher.h"
#include "ProxyConfig.h"
#include "HostStatus.h"
#include "HTTP.h"
#include "HttpTransact.h"
#include "I_Machine.h"
#include "tscore/Filenames.h"
#define MAX_SIMPLE_RETRIES 5
#define MAX_UNAVAILABLE_SERVER_RETRIES 5
using P_table = ControlMatcher<ParentRecord, ParentResult>;
// Global Vars for Parent Selection
static const char modulePrefix[] = "[ParentSelection]";
static ConfigUpdateHandler<ParentConfig> *parentConfigUpdate = nullptr;
static int self_detect = 2;
// Config var names
static const char *file_var = "proxy.config.http.parent_proxy.file";
static const char *default_var = "proxy.config.http.parent_proxies";
static const char *retry_var = "proxy.config.http.parent_proxy.retry_time";
static const char *threshold_var = "proxy.config.http.parent_proxy.fail_threshold";
//
// Config Callback Prototypes
//
enum ParentCB_t {
PARENT_FILE_CB,
PARENT_DEFAULT_CB,
PARENT_RETRY_CB,
PARENT_ENABLE_CB,
PARENT_THRESHOLD_CB,
PARENT_DNS_ONLY_CB,
};
ParentSelectionPolicy::ParentSelectionPolicy()
{
int32_t retry_time = 0;
int32_t fail_threshold = 0;
// Handle parent timeout
REC_ReadConfigInteger(retry_time, retry_var);
ParentRetryTime = retry_time;
// Handle the fail threshold
REC_ReadConfigInteger(fail_threshold, threshold_var);
FailThreshold = fail_threshold;
}
ParentConfigParams::ParentConfigParams(P_table *_parent_table) : parent_table(_parent_table), DefaultParent(nullptr), policy()
{
char *default_val = nullptr;
// Handle default parent
REC_ReadConfigStringAlloc(default_val, default_var);
DefaultParent = createDefaultParent(default_val);
ats_free(default_val);
}
ParentConfigParams::~ParentConfigParams()
{
if (parent_table) {
Debug("parent_select", "~ParentConfigParams(): releasing parent_table %p", parent_table);
}
delete parent_table;
delete DefaultParent;
}
bool
ParentConfigParams::apiParentExists(HttpRequestData *rdata)
{
return (rdata->api_info && rdata->api_info->parent_proxy_name != nullptr && rdata->api_info->parent_proxy_port > 0);
}
void
ParentConfigParams::findParent(HttpRequestData *rdata, ParentResult *result, unsigned int fail_threshold, unsigned int retry_time)
{
P_table *tablePtr = parent_table;
ParentRecord *defaultPtr = DefaultParent;
ParentRecord *rec;
// Check to see if the parent was set through the
// api
if (apiParentExists(rdata)) {
result->result = PARENT_SPECIFIED;
result->hostname = rdata->api_info->parent_proxy_name;
result->port = rdata->api_info->parent_proxy_port;
result->rec = extApiRecord;
result->start_parent = 0;
result->last_parent = 0;
result->url = rdata->get_host();
Debug("parent_select", "Result for %s was API set parent %s:%d", rdata->get_host(), result->hostname, result->port);
return;
}
// Initialize the result structure
result->reset();
tablePtr->Match(rdata, result);
rec = result->rec;
result->url = rdata->get_host();
if (rec == nullptr) {
// No parents were found
//
// If there is a default parent, use it
if (defaultPtr != nullptr) {
rec = result->rec = defaultPtr;
} else {
result->result = PARENT_DIRECT;
Debug("parent_select", "Returning PARENT_DIRECT (no parents were found)");
return;
}
}
if (rec != extApiRecord) {
selectParent(true, result, rdata, fail_threshold, retry_time);
}
switch (result->result) {
case PARENT_UNDEFINED:
Debug("parent_select", "PARENT_UNDEFINED");
Debug("parent_select", "Result for %s was %s", result->url, ParentResultStr[result->result]);
break;
case PARENT_FAIL:
Debug("parent_select", "PARENT_FAIL");
break;
case PARENT_DIRECT:
Debug("parent_select", "PARENT_DIRECT");
Debug("parent_select", "Result for %s was %s", result->url, ParentResultStr[result->result]);
break;
case PARENT_SPECIFIED:
Debug("parent_select", "PARENT_SPECIFIED");
Debug("parent_select", "Result for %s was parent %s:%d", result->url, result->hostname, result->port);
break;
default:
// Handled here:
// PARENT_AGENT
break;
}
}
void
ParentConfigParams::nextParent(HttpRequestData *rdata, ParentResult *result, unsigned int fail_threshold, unsigned int retry_time)
{
P_table *tablePtr = parent_table;
Debug("parent_select", "ParentConfigParams::nextParent(): parent_table: %p, result->rec: %p", parent_table, result->rec);
// Make sure that we are being called back with a
// result structure with a parent
ink_assert(result->result == PARENT_SPECIFIED);
if (result->result != PARENT_SPECIFIED) {
result->result = PARENT_FAIL;
return;
}
// If we were set through the API we currently have not failover
// so just return fail
if (result->is_api_result()) {
Debug("parent_select", "Retry result for %s was %s", rdata->get_host(), ParentResultStr[result->result]);
result->result = PARENT_FAIL;
return;
}
Debug("parent_select", "ParentConfigParams::nextParent(): result->r: %d, tablePtr: %p", result->result, tablePtr);
// Find the next parent in the array
Debug("parent_select", "Calling selectParent() from nextParent");
selectParent(false, result, rdata, fail_threshold, retry_time);
const char *host = rdata->get_host();
switch (result->result) {
case PARENT_UNDEFINED:
Debug("parent_select", "PARENT_UNDEFINED");
Debug("parent_select", "Retry result for %s was %s", host, ParentResultStr[result->result]);
break;
case PARENT_FAIL:
Debug("parent_select", "PARENT_FAIL");
Debug("parent_select", "Retry result for %s was %s", host, ParentResultStr[result->result]);
break;
case PARENT_DIRECT:
Debug("parent_select", "PARENT_DIRECT");
Debug("parent_select", "Retry result for %s was %s", host, ParentResultStr[result->result]);
break;
case PARENT_SPECIFIED:
Debug("parent_select", "Retry result for %s was parent %s:%d", host, result->hostname, result->port);
break;
default:
// Handled here:
// PARENT_AGENT
break;
}
}
bool
ParentConfigParams::parentExists(HttpRequestData *rdata)
{
P_table *tablePtr = parent_table;
ParentRecord *rec = nullptr;
ParentResult result;
// Initialize the result structure;
result.reset();
tablePtr->Match(rdata, &result);
rec = result.rec;
if (rec == nullptr) {
Debug("parent_select", "No matching parent record was found for the request.");
return false;
}
if (rec->num_parents > 0) {
for (int ii = 0; ii < rec->num_parents; ii++) {
if (rec->parents[ii].available) {
Debug("parent_select", "found available parent: %s", rec->parents[ii].hostname);
return true;
}
}
}
if (rec->secondary_parents && rec->num_secondary_parents > 0) {
for (int ii = 0; ii < rec->num_secondary_parents; ii++) {
if (rec->secondary_parents[ii].available) {
Debug("parent_select", "found available parent: %s", rec->secondary_parents[ii].hostname);
return true;
}
}
}
return false;
}
int ParentConfig::m_id = 0;
void
ParentConfig::startup()
{
parentConfigUpdate = new ConfigUpdateHandler<ParentConfig>();
// Load the initial configuration
reconfigure();
// Setup the callbacks for reconfiuration
// parent table
parentConfigUpdate->attach(file_var);
// default parent
parentConfigUpdate->attach(default_var);
// Retry time
parentConfigUpdate->attach(retry_var);
// Fail Threshold
parentConfigUpdate->attach(threshold_var);
}
void
ParentConfig::reconfigure()
{
Note("%s loading ...", ts::filename::PARENT);
ParentConfigParams *params = nullptr;
// Allocate parent table
P_table *pTable = new P_table(file_var, modulePrefix, &http_dest_tags);
params = new ParentConfigParams(pTable);
ink_assert(params != nullptr);
m_id = configProcessor.set(m_id, params);
if (is_debug_tag_set("parent_config")) {
ParentConfig::print();
}
Note("%s finished loading", ts::filename::PARENT);
}
void
ParentConfig::print()
{
ParentConfigParams *params = ParentConfig::acquire();
printf("Parent Selection Config\n");
printf("\tRetryTime %d\n", params->policy.ParentRetryTime);
if (params->DefaultParent == nullptr) {
printf("\tNo Default Parent\n");
} else {
printf("\tDefault Parent:\n");
params->DefaultParent->Print();
}
printf(" ");
params->parent_table->Print();
ParentConfig::release(params);
}
UnavailableServerResponseCodes::UnavailableServerResponseCodes(char *val)
{
Tokenizer pTok(", \t\r");
int numTok = 0, c;
if (val == nullptr) {
Warning("UnavailableServerResponseCodes - unavailable_server_retry_responses is null loading default 503 code.");
codes.push_back(HTTP_STATUS_SERVICE_UNAVAILABLE);
return;
}
numTok = pTok.Initialize(val, SHARE_TOKS);
if (numTok == 0) {
c = atoi(val);
if (c > 499 && c < 600) {
codes.push_back(HTTP_STATUS_SERVICE_UNAVAILABLE);
}
}
for (int i = 0; i < numTok; i++) {
c = atoi(pTok[i]);
if (c > 499 && c < 600) {
Debug("parent_select", "loading response code: %d", c);
codes.push_back(c);
} else {
Warning("UnavailableServerResponseCodes received non-5xx code '%s', ignoring!", pTok[i]);
}
}
std::sort(codes.begin(), codes.end());
}
SimpleRetryResponseCodes::SimpleRetryResponseCodes(char *val)
{
Tokenizer pTok(", \t\r");
int numTok = 0, c;
if (val == nullptr) {
Warning("SimpleRetryResponseCodes - simple_server_retry_responses is null loading default 404 code.");
codes.push_back(HTTP_STATUS_NOT_FOUND);
return;
}
numTok = pTok.Initialize(val, SHARE_TOKS);
if (numTok == 0) {
c = atoi(val);
if (c > 399 && c < 600) {
codes.push_back(HTTP_STATUS_NOT_FOUND);
}
}
for (int i = 0; i < numTok; i++) {
c = atoi(pTok[i]);
if (c > 399 && c < 600) {
Debug("parent_select", "loading simple response code: %d", c);
codes.push_back(c);
} else {
Warning("SimpleRetryResponseCodes received non-4xx or 5xx code '%s', ignoring!", pTok[i]);
}
}
std::sort(codes.begin(), codes.end());
}
void
ParentRecord::PreProcessParents(const char *val, const int line_num, char *buf, size_t len)
{
char *_val = ats_strndup(val, strlen(val));
char fqdn[TS_MAX_HOST_NAME_LEN] = {0}, *nm, *token, *savePtr;
std::string str;
Machine *machine = Machine::instance();
constexpr char PARENT_DELIMITERS[] = ";, ";
HostStatus &hs = HostStatus::instance();
token = strtok_r(_val, PARENT_DELIMITERS, &savePtr);
while (token != nullptr) {
if ((nm = strchr(token, ':')) != nullptr) {
size_t length = (nm - token);
ink_assert(length < sizeof(fqdn));
memset(fqdn, 0, sizeof(fqdn));
strncpy(fqdn, token, length);
if (self_detect && machine->is_self(std::string_view(fqdn))) {
if (self_detect == 1) {
Debug("parent_select", "token: %s, matches this machine. Removing self from parent list at line %d", fqdn, line_num);
token = strtok_r(nullptr, PARENT_DELIMITERS, &savePtr);
continue;
} else {
Debug("parent_select", "token: %s, matches this machine. Marking down self from parent list at line %d", fqdn, line_num);
hs.setHostStatus(fqdn, TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT);
}
}
} else {
if (self_detect && machine->is_self(std::string_view(token))) {
if (self_detect == 1) {
Debug("parent_select", "token: %s, matches this machine. Removing self from parent list at line %d", token, line_num);
token = strtok_r(nullptr, PARENT_DELIMITERS, &savePtr);
continue;
} else {
Debug("parent_select", "token: %s, matches this machine. Marking down self from parent list at line %d", token,
line_num);
hs.setHostStatus(token, TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT);
}
}
}
str += token;
str += ";";
token = strtok_r(nullptr, PARENT_DELIMITERS, &savePtr);
}
strncpy(buf, str.c_str(), len);
ats_free(_val);
}
// const char* ParentRecord::ProcessParents(char* val, bool isPrimary)
//
// Reads in the value of a "round-robin" or "order"
// directive and parses out the individual parents
// allocates and builds the this->parents array or
// this->secondary_parents based upon the isPrimary
// boolean.
//
// Returns NULL on success and a static error string
// on failure
//
const char *
ParentRecord::ProcessParents(char *val, bool isPrimary)
{
Tokenizer pTok(",; \t\r");
int numTok = 0;
const char *current = nullptr;
int port = 0;
char *tmp = nullptr, *tmp2 = nullptr, *tmp3 = nullptr;
const char *errPtr = nullptr;
float weight = DEFAULT_PARENT_WEIGHT;
if (parents != nullptr && isPrimary == true) {
return "Can not specify more than one set of parents";
}
if (secondary_parents != nullptr && isPrimary == false) {
return "Can not specify more than one set of secondary parents";
}
numTok = pTok.Initialize(val, SHARE_TOKS);
if (numTok == 0) {
return "No parents specified";
}
// Allocate the parents array
if (isPrimary) {
this->parents = static_cast<pRecord *>(ats_malloc(sizeof(pRecord) * numTok));
} else {
this->secondary_parents = static_cast<pRecord *>(ats_malloc(sizeof(pRecord) * numTok));
}
// Loop through the set of parents specified
//
for (int i = 0; i < numTok; i++) {
weight = DEFAULT_PARENT_WEIGHT; // reset weight to the default
current = pTok[i];
// Find the parent port
tmp = const_cast<char *>(strchr(current, ':'));
if (tmp == nullptr) {
errPtr = "No parent port specified";
goto MERROR;
}
// Read the parent port
// coverity[secure_coding]
if (sscanf(tmp + 1, "%d", &port) != 1) {
errPtr = "Malformed parent port";
goto MERROR;
}
// See if there is an optional parent weight
tmp2 = const_cast<char *>(strchr(current, '|'));
if (tmp2) {
if (sscanf(tmp2 + 1, "%f", &weight) != 1) {
errPtr = "Malformed parent weight";
goto MERROR;
}
}
tmp3 = const_cast<char *>(strchr(current, '&'));
// Make sure that is no garbage beyond the parent
// port or weight
if (!tmp3) {
char *scan;
if (tmp2) {
scan = tmp2 + 1;
} else {
scan = tmp + 1;
}
for (; *scan != '\0' && (ParseRules::is_digit(*scan) || *scan == '.'); scan++) {
;
}
for (; *scan != '\0' && ParseRules::is_wslfcr(*scan); scan++) {
;
}
if (*scan != '\0') {
errPtr = "Garbage trailing entry or invalid separator";
goto MERROR;
}
}
// Check to make sure that the string will fit in the
// pRecord
if (tmp - current > MAXDNAME) {
errPtr = "Parent hostname is too long";
goto MERROR;
} else if (tmp - current == 0) {
errPtr = "Parent string is empty";
goto MERROR;
}
// Update the pRecords
if (isPrimary) {
memcpy(this->parents[i].hostname, current, tmp - current);
this->parents[i].hostname[tmp - current] = '\0';
this->parents[i].port = port;
this->parents[i].failedAt = 0;
this->parents[i].failCount = 0;
this->parents[i].scheme = scheme;
this->parents[i].idx = i;
this->parents[i].name = this->parents[i].hostname;
this->parents[i].available = true;
this->parents[i].weight = weight;
if (tmp3) {
memcpy(this->parents[i].hash_string, tmp3 + 1, strlen(tmp3));
this->parents[i].name = this->parents[i].hash_string;
}
} else {
memcpy(this->secondary_parents[i].hostname, current, tmp - current);
this->secondary_parents[i].hostname[tmp - current] = '\0';
this->secondary_parents[i].port = port;
this->secondary_parents[i].failedAt = 0;
this->secondary_parents[i].failCount = 0;
this->secondary_parents[i].scheme = scheme;
this->secondary_parents[i].idx = i;
this->secondary_parents[i].name = this->secondary_parents[i].hostname;
this->secondary_parents[i].available = true;
this->secondary_parents[i].weight = weight;
if (tmp3) {
memcpy(this->secondary_parents[i].hash_string, tmp3 + 1, strlen(tmp3));
this->secondary_parents[i].name = this->secondary_parents[i].hash_string;
}
}
tmp3 = nullptr;
}
if (isPrimary) {
num_parents = numTok;
} else {
num_secondary_parents = numTok;
}
return nullptr;
MERROR:
if (isPrimary) {
ats_free(parents);
parents = nullptr;
} else {
ats_free(secondary_parents);
secondary_parents = nullptr;
}
return errPtr;
}
// bool ParentRecord::DefaultInit(char* val)
//
// Creates the record for a default parent proxy rule
/// established by a config variable
//
// matcher_line* line_info - contains the value of
// proxy.config.http.parent_proxies
//
// Returns true on success and false on failure
//
bool
ParentRecord::DefaultInit(char *val)
{
const char *errPtr;
char *errBuf;
bool alarmAlready = false;
this->go_direct = true;
this->ignore_query = false;
this->scheme = nullptr;
this->parent_is_proxy = true;
errPtr = ProcessParents(val, true);
if (errPtr != nullptr) {
errBuf = static_cast<char *>(ats_malloc(1024));
snprintf(errBuf, 1024, "%s %s for default parent proxy", modulePrefix, errPtr);
SignalError(errBuf, alarmAlready);
ats_free(errBuf);
return false;
} else {
ParentRR_t round_robin = P_NO_ROUND_ROBIN;
Debug("parent_select", "allocating ParentRoundRobin() lookup strategy.");
selection_strategy = new ParentRoundRobin(this, round_robin);
return true;
}
}
// Result ParentRecord::Init(matcher_line* line_info)
//
// matcher_line* line_info - contains parsed label/value
// pairs of the current cache.config line
//
// Returns NULL if everything is OK
// Otherwise, returns an error string that the caller MUST
// DEALLOCATE with ats_free()
//
Result
ParentRecord::Init(matcher_line *line_info)
{
const char *errPtr = nullptr;
const char *tmp;
char *label;
char *val;
char parent_buf[16384] = {0};
bool used = false;
ParentRR_t round_robin = P_NO_ROUND_ROBIN;
char buf[128];
RecInt rec_self_detect = 2;
this->line_num = line_info->line_num;
this->scheme = nullptr;
if (RecGetRecordInt("proxy.config.http.parent_proxy.self_detect", &rec_self_detect) == REC_ERR_OKAY) {
self_detect = static_cast<int>(rec_self_detect);
}
for (int i = 0; i < MATCHER_MAX_TOKENS; i++) {
used = false;
label = line_info->line[0][i];
val = line_info->line[1][i];
if (label == nullptr) {
continue;
}
if (strcasecmp(label, "round_robin") == 0) {
if (strcasecmp(val, "true") == 0) {
round_robin = P_HASH_ROUND_ROBIN;
} else if (strcasecmp(val, "strict") == 0) {
round_robin = P_STRICT_ROUND_ROBIN;
} else if (strcasecmp(val, "false") == 0) {
round_robin = P_NO_ROUND_ROBIN;
} else if (strcasecmp(val, "consistent_hash") == 0) {
round_robin = P_CONSISTENT_HASH;
} else if (strcasecmp(val, "latched") == 0) {
round_robin = P_LATCHED_ROUND_ROBIN;
} else {
round_robin = P_NO_ROUND_ROBIN;
errPtr = "invalid argument to round_robin directive";
}
used = true;
} else if (strcasecmp(label, "parent") == 0 || strcasecmp(label, "primary_parent") == 0) {
PreProcessParents(val, line_num, parent_buf, sizeof(parent_buf) - 1);
errPtr = ProcessParents(parent_buf, true);
used = true;
} else if (strcasecmp(label, "secondary_parent") == 0) {
PreProcessParents(val, line_num, parent_buf, sizeof(parent_buf) - 1);
errPtr = ProcessParents(parent_buf, false);
used = true;
} else if (strcasecmp(label, "go_direct") == 0) {
if (strcasecmp(val, "false") == 0) {
go_direct = false;
} else if (strcasecmp(val, "true") != 0) {
errPtr = "invalid argument to go_direct directive";
} else {
go_direct = true;
}
used = true;
} else if (strcasecmp(label, "qstring") == 0) {
// qstring=ignore | consider
if (strcasecmp(val, "ignore") == 0) {
this->ignore_query = true;
} else {
this->ignore_query = false;
}
used = true;
} else if (strcasecmp(label, "parent_is_proxy") == 0) {
if (strcasecmp(val, "false") == 0) {
parent_is_proxy = false;
} else if (strcasecmp(val, "true") != 0) {
errPtr = "invalid argument to parent_is_proxy directive";
} else {
parent_is_proxy = true;
}
used = true;
} else if (strcasecmp(label, "parent_retry") == 0) {
if (strcasecmp(val, "simple_retry") == 0) {
parent_retry = PARENT_RETRY_SIMPLE;
} else if (strcasecmp(val, "unavailable_server_retry") == 0) {
parent_retry = PARENT_RETRY_UNAVAILABLE_SERVER;
} else if (strcasecmp(val, "both") == 0) {
parent_retry = PARENT_RETRY_BOTH;
} else {
errPtr = "invalid argument to parent_retry directive.";
}
used = true;
} else if (strcasecmp(label, "unavailable_server_retry_responses") == 0 && unavailable_server_retry_responses == nullptr) {
unavailable_server_retry_responses = new UnavailableServerResponseCodes(val);
used = true;
} else if (strcasecmp(label, "simple_server_retry_responses") == 0 && simple_server_retry_responses == nullptr) {
simple_server_retry_responses = new SimpleRetryResponseCodes(val);
used = true;
} else if (strcasecmp(label, "max_simple_retries") == 0) {
int v = atoi(val);
if (v >= 1 && v < MAX_SIMPLE_RETRIES) {
max_simple_retries = v;
used = true;
} else {
snprintf(buf, sizeof(buf), "invalid argument to max_simple_retries. Argument must be between 1 and %d.",
MAX_SIMPLE_RETRIES);
errPtr = buf;
}
} else if (strcasecmp(label, "max_unavailable_server_retries") == 0) {
int v = atoi(val);
if (v >= 1 && v < MAX_UNAVAILABLE_SERVER_RETRIES) {
max_unavailable_server_retries = v;
used = true;
} else {
snprintf(buf, sizeof(buf), "invalid argument to max_unavailable_server_retries. Argument must be between 1 and %d.",
MAX_UNAVAILABLE_SERVER_RETRIES);
errPtr = buf;
}
} else if (strcasecmp(label, "secondary_mode") == 0) {
int v = atoi(val);
secondary_mode = v;
used = true;
} else if (strcasecmp(label, "ignore_self_detect") == 0) {
if (strcasecmp(val, "true") == 0) {
ignore_self_detect = true;
} else {
ignore_self_detect = false;
}
used = true;
}
// Report errors generated by ProcessParents();
if (errPtr != nullptr) {
return Result::failure("%s %s at line %d", modulePrefix, errPtr, line_num);
}
if (used == true) {
// Consume the label/value pair we used
line_info->line[0][i] = nullptr;
line_info->num_el--;
}
}
// delete unavailable_server_retry_responses if unavailable_server_retry is not enabled.
if (unavailable_server_retry_responses != nullptr && !(parent_retry & PARENT_RETRY_UNAVAILABLE_SERVER)) {
Warning("%s ignoring unavailable_server_retry_responses directive on line %d, as unavailable_server_retry is not enabled.",
modulePrefix, line_num);
delete unavailable_server_retry_responses;
unavailable_server_retry_responses = nullptr;
} else if (unavailable_server_retry_responses == nullptr && (parent_retry & PARENT_RETRY_UNAVAILABLE_SERVER)) {
// initialize UnavailableServerResponseCodes to the default value if unavailable_server_retry is enabled.
Warning("%s initializing UnavailableServerResponseCodes on line %d to 503 default.", modulePrefix, line_num);
unavailable_server_retry_responses = new UnavailableServerResponseCodes(nullptr);
}
// delete simple_server_retry_responses if simple_retry is not enabled.
if (simple_server_retry_responses != nullptr && !(parent_retry & PARENT_RETRY_SIMPLE)) {
Warning("%s ignore simple_server_Retry_responses directive on line %d, as simple_server_retry is not enabled.", modulePrefix,
line_num);
delete simple_server_retry_responses;
simple_server_retry_responses = nullptr;
} else if (simple_server_retry_responses == nullptr && (parent_retry & PARENT_RETRY_SIMPLE)) {
// initialize simple server respones codes to the default value if simple_retry is enabled.
Warning("%s initializing SimpleRetryResponseCodes on line %d to 404 default.", modulePrefix, line_num);
simple_server_retry_responses = new SimpleRetryResponseCodes(nullptr);
}
if (this->parents == nullptr && go_direct == false) {
return Result::failure("%s No parent specified in %s at line %d", modulePrefix, ts::filename::PARENT, line_num);
}
// Process any modifiers to the directive, if they exist
if (line_info->num_el > 0) {
tmp = ProcessModifiers(line_info);
if (tmp != nullptr) {
return Result::failure("%s %s at line %d in %s", modulePrefix, tmp, line_num, ts::filename::PARENT);
}
// record SCHEME modifier if present.
// NULL if not present
this->scheme = this->getSchemeModText();
if (this->scheme != nullptr) {
// update parent entries' schemes
for (int j = 0; j < num_parents; j++) {
this->parents[j].scheme = this->scheme;
}
}
}
switch (round_robin) {
// ParentRecord.round_robin defaults to P_NO_ROUND_ROBIN when round_robin
// is not set in parent.config. Therefore ParentRoundRobin is the default
// strategy. If setting go_direct to true, there should be no parent list
// in parent.config and ParentRoundRobin::lookup will set parent_result->r
// to PARENT_DIRECT.
case P_NO_ROUND_ROBIN:
case P_STRICT_ROUND_ROBIN:
case P_HASH_ROUND_ROBIN:
case P_LATCHED_ROUND_ROBIN:
Debug("parent_select", "allocating ParentRoundRobin() lookup strategy.");
selection_strategy = new ParentRoundRobin(this, round_robin);
break;
case P_CONSISTENT_HASH:
Debug("parent_select", "allocating ParentConsistentHash() lookup strategy.");
selection_strategy = new ParentConsistentHash(this);
break;
default:
ink_release_assert(0);
}
return Result::ok();
}
// void ParentRecord::UpdateMatch(ParentResult* result, RequestData* rdata);
//
// Updates the record ptr in result if the this element
// appears later in the file
//
void
ParentRecord::UpdateMatch(ParentResult *result, RequestData *rdata)
{
if (this->CheckForMatch((HttpRequestData *)rdata, result->line_number) == true) {
result->rec = this;
result->line_number = this->line_num;
Debug("parent_select", "Matched with %p parent node from line %d", this, this->line_num);
}
}
ParentRecord::~ParentRecord()
{
ats_free(parents);
ats_free(secondary_parents);
delete selection_strategy;
delete unavailable_server_retry_responses;
}
void
ParentRecord::Print() const
{
printf("\t\t");
for (int i = 0; i < num_parents; i++) {
printf(" %s:%d|%f&%s ", parents[i].hostname, parents[i].port, parents[i].weight, parents[i].name);
}
printf(" direct=%s\n", (go_direct == true) ? "true" : "false");
printf(" parent_is_proxy=%s\n", (parent_is_proxy == true) ? "true" : "false");
}
// ParentRecord* createDefaultParent(char* val)
//
// Atttemtps to allocate and init new ParentRecord
// for a default parent
//
// Returns a pointer to the new record on success
// and NULL on failure
//
ParentRecord *
createDefaultParent(char *val)
{
ParentRecord *newRec;
if (val == nullptr || *val == '\0') {
return nullptr;
}
newRec = new ParentRecord;
if (newRec->DefaultInit(val) == true) {
return newRec;
} else {
delete newRec;
return nullptr;
}
}
//
// ParentConfig equivalent functions for SocksServerConfig
//
int SocksServerConfig::m_id = 0;
static Ptr<ProxyMutex> socks_server_reconfig_mutex;
void
SocksServerConfig::startup()
{
socks_server_reconfig_mutex = new_ProxyMutex();
// Load the initial configuration
reconfigure();
/* Handle update functions later. Socks does not yet support config update */
}
static int
setup_socks_servers(ParentRecord *rec_arr, int len)
{
/* This changes hostnames into ip addresses and sets go_direct to false */
for (int j = 0; j < len; j++) {
rec_arr[j].go_direct = false;
pRecord *pr = rec_arr[j].parents;
int n_parents = rec_arr[j].num_parents;
for (int i = 0; i < n_parents; i++) {
IpEndpoint ip4, ip6;
if (0 == ats_ip_getbestaddrinfo(pr[i].hostname, &ip4, &ip6)) {
IpEndpoint *ip = ats_is_ip6(&ip6) ? &ip6 : &ip4;
ats_ip_ntop(ip, pr[i].hostname, MAXDNAME + 1);
} else {
Warning("Could not resolve socks server name \"%s\". "
"Please correct it",
pr[i].hostname);
snprintf(pr[i].hostname, MAXDNAME + 1, "255.255.255.255");
}
}
}
return 0;
}
void
SocksServerConfig::reconfigure()
{
Note("%s loading ...", ts::filename::SOCKS);
char *default_val = nullptr;
int retry_time = 30;
int fail_threshold;
ParentConfigParams *params = nullptr;
// Allocate parent table
P_table *pTable = new P_table("proxy.config.socks.socks_config_file", "[Socks Server Selection]", &socks_server_tags);
params = new ParentConfigParams(pTable);
ink_assert(params != nullptr);
// Handle default parent
REC_ReadConfigStringAlloc(default_val, "proxy.config.socks.default_servers");
params->DefaultParent = createDefaultParent(default_val);
ats_free(default_val);
if (params->DefaultParent) {
setup_socks_servers(params->DefaultParent, 1);
}
if (params->parent_table->ipMatch) {
setup_socks_servers(params->parent_table->ipMatch->data_array, params->parent_table->ipMatch->array_len);
}
// Handle parent timeout
REC_ReadConfigInteger(retry_time, "proxy.config.socks.server_retry_time");
params->policy.ParentRetryTime = retry_time;
// Handle the fail threshold
REC_ReadConfigInteger(fail_threshold, "proxy.config.socks.server_fail_threshold");
params->policy.FailThreshold = fail_threshold;
m_id = configProcessor.set(m_id, params);
if (is_debug_tag_set("parent_config")) {
SocksServerConfig::print();
}
Note("%s finished loading", ts::filename::SOCKS);
}
void
SocksServerConfig::print()
{
ParentConfigParams *params = SocksServerConfig::acquire();
printf("Parent Selection Config for Socks Server\n");
printf("\tRetryTime %d\n", params->policy.ParentRetryTime);
if (params->DefaultParent == nullptr) {
printf("\tNo Default Parent\n");
} else {
printf("\tDefault Parent:\n");
params->DefaultParent->Print();
}
printf(" ");
params->parent_table->Print();
SocksServerConfig::release(params);
}
#define TEST_FAIL(str) \
{ \
printf("%d: %s\n", test_id, str); \
err = REGRESSION_TEST_FAILED; \
}
void
request_to_data(HttpRequestData *req, sockaddr const *srcip, sockaddr const *dstip, const char *str)
{
HTTPParser parser;
ink_zero(req->src_ip);
ats_ip_copy(&req->src_ip.sa, srcip);
ink_zero(req->dest_ip);
ats_ip_copy(&req->dest_ip.sa, dstip);
req->hdr = new HTTPHdr;
http_parser_init(&parser);
req->hdr->parse_req(&parser, &str, str + strlen(str), true);
http_parser_clear(&parser);
}
static int passes;
static int fails;
// Parenting Tests
EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, int /* intensity_level ATS_UNUSED */, int *pstatus)
{
// first, set everything up
*pstatus = REGRESSION_TEST_INPROGRESS;
ParentConfig config;
P_table *ParentTable = nullptr;
ParentConfigParams *params = nullptr;
passes = fails = 0;
config.startup();
char tbl[2048];
HttpRequestData *request = nullptr;
ParentResult *result = nullptr;
unsigned int fail_threshold = 1;
unsigned int retry_time = 5;
#define T(x) \
do { \
ink_strlcat(tbl, x, sizeof(tbl)); \
} while (0)
#define REBUILD \
do { \
delete params; \
ParentTable = new P_table("", "ParentSelection Unit Test Table", &http_dest_tags, \
ALLOW_HOST_TABLE | ALLOW_REGEX_TABLE | ALLOW_URL_TABLE | ALLOW_IP_TABLE | DONT_BUILD_TABLE); \
ParentTable->BuildTableFromString(tbl); \
RecSetRecordInt("proxy.config.http.parent_proxy.fail_threshold", fail_threshold, REC_SOURCE_DEFAULT); \
RecSetRecordInt("proxy.config.http.parent_proxy.retry_time", retry_time, REC_SOURCE_DEFAULT); \
params = new ParentConfigParams(ParentTable); \
} while (0)
#define REINIT \
do { \
if (request != nullptr) { \
delete request->hdr; \
/* ats_free(request->hostname_str); */ \
delete request->api_info; \
} \
delete request; \
delete result; \
request = new HttpRequestData(); \
result = new ParentResult(); \
if (!result || !request) { \
(void)printf("Allocation failed\n"); \
return; \
} \
} while (0)
#define ST(x) \
do { \
printf("*** TEST %d *** STARTING ***\n", x); \
} while (0)
#define RE(x, y) \
do { \
if (x) { \
printf("*** TEST %d *** PASSED ***\n", y); \
passes++; \
} else { \
printf("*** TEST %d *** FAILED *** FAILED *** FAILED ***\n", y); \
fails++; \
} \
} while (0)
#define FP \
do { \
params->findParent(request, result, fail_threshold, retry_time); \
} while (0)
#define SET_MAX_RETRIERS(x) \
do { \
RecSetRecordInt("proxy.config.http.parent_proxy.max_trans_retries", x, REC_SOURCE_DEFAULT); \
} while (0)
// start tests by marking up all tests hosts that will be marked down
// as part of testing. This will insure that test hosts are not loaded
// from records.snap as DOWN due to previous testing.
//
HostStatus &_st = HostStatus::instance();
_st.setHostStatus("furry", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("fluffy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("frisky", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("curly", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
// Test 1
SET_MAX_RETRIERS(20);
tbl[0] = '\0';
ST(1);
T("dest_domain=. parent=red:37412,orange:37412,yellow:37412 round_robin=strict\n");
REBUILD;
int c, red = 0, orange = 0, yellow = 0;
for (c = 0; c < 21; c++) {
REINIT;
br(request, "fruit_basket.net");
FP;
red += verify(result, PARENT_SPECIFIED, "red", 37412);
orange += verify(result, PARENT_SPECIFIED, "orange", 37412);
yellow += verify(result, PARENT_SPECIFIED, "yellow", 37412);
}
RE(((red == 7) && (orange == 7) && (yellow == 7)), 1);
// Test 2
ST(2);
tbl[0] = '\0';
T("dest_domain=. parent=green:4325,blue:4325,indigo:4325,violet:4325 round_robin=false\n");
REBUILD;
int g = 0, b = 0, i = 0, v = 0;
for (c = 0; c < 17; c++) {
REINIT;
br(request, "fruit_basket.net");
FP;
g += verify(result, PARENT_SPECIFIED, "green", 4325);
b += verify(result, PARENT_SPECIFIED, "blue", 4325);
i += verify(result, PARENT_SPECIFIED, "indigo", 4325);
v += verify(result, PARENT_SPECIFIED, "violet", 4325);
}
RE((((g == 17) && !b && !i && !v) || (!g && (b == 17) && !i && !v) || (!g && !b && (i == 17) && !v) ||
(!g && !b && !i && (v == 17))),
2);
// Test 3 - 6 Parenting Table
tbl[0] = '\0';
#define TEST_IP4_ADDR "209.131.62.14"
#define TEST_IP6_ADDR "BEEF:DEAD:ABBA:CAFE:1337:1E1F:5EED:C0FF"
T("dest_ip=" TEST_IP4_ADDR " parent=cat:37,dog:24 round_robin=strict\n"); /* L1 */
T("dest_ip=" TEST_IP6_ADDR " parent=zwoop:37,jMCg:24 round_robin=strict\n"); /* L1 */
T("dest_host=www.pilot.net parent=pilot_net:80\n"); /* L2 */
T("url_regex=snoopy parent=odie:80,garfield:80 round_robin=true\n"); /* L3 */
T("dest_domain=i.am parent=amy:80,katie:80,carissa:771 round_robin=false\n"); /* L4 */
T("dest_domain=microsoft.net time=03:00-22:10 parent=zoo.net:341\n"); /* L5 */
T("dest_domain=microsoft.net time=0:00-02:59 parent=zoo.net:347\n"); /* L6 */
T("dest_domain=microsoft.net time=22:11-23:59 parent=zoo.edu:111\n"); /* L7 */
T("dest_domain=imac.net port=819 parent=genie:80 round_robin=strict\n"); /* L8 */
T("dest_ip=172.34.61.211 port=3142 parent=orangina:80 go_direct=false\n"); /* L9 */
T("url_regex=miffy prefix=furry/rabbit parent=nintje:80 go_direct=false\n"); /* L10 */
T("url_regex=kitty suffix=tif parent=hello:80 round_robin=strict go_direct=false\n"); /* L11 */
T("url_regex=cyclops method=get parent=turkey:80\n"); /* L12 */
T("url_regex=cyclops method=post parent=club:80\n"); /* L13 */
T("url_regex=cyclops method=put parent=sandwich:80\n"); /* L14 */
T("url_regex=cyclops method=trace parent=mayo:80\n"); /* L15 */
T("dest_host=pluto scheme=HTTP parent=strategy:80\n"); /* L16 */
REBUILD;
// Test 3
IpEndpoint ip;
ats_ip_pton(TEST_IP4_ADDR, &ip.sa);
ST(3);
REINIT;
br(request, "numeric_host", &ip.sa);
FP;
RE(verify(result, PARENT_SPECIFIED, "cat", 37) + verify(result, PARENT_SPECIFIED, "dog", 24), 3);
ats_ip_pton(TEST_IP6_ADDR, &ip.sa);
ST(4);
REINIT;
br(request, "numeric_host", &ip.sa);
FP;
RE(verify(result, PARENT_SPECIFIED, "zwoop", 37) + verify(result, PARENT_SPECIFIED, "jMCg", 24), 4);
// Test 5
ST(5);
REINIT;
br(request, "www.pilot.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "pilot_net", 80), 5);
// Test 6
ST(6);
REINIT;
br(request, "www.snoopy.net");
const char *snoopy_dog = "http://www.snoopy.com/";
request->hdr->url_set(snoopy_dog, strlen(snoopy_dog));
FP;
RE(verify(result, PARENT_SPECIFIED, "odie", 80) + verify(result, PARENT_SPECIFIED, "garfield", 80), 5);
// Test 7
ST(7);
REINIT;
br(request, "a.rabbit.i.am");
FP;
RE(verify(result, PARENT_SPECIFIED, "amy", 80) + verify(result, PARENT_SPECIFIED, "katie", 80) +
verify(result, PARENT_SPECIFIED, "carissa", 771),
6);
// Test 6+ BUGBUG needs to be fixed
// ST(7); REINIT;
// br(request, "www.microsoft.net");
// FP; RE( verify(result,PARENT_SPECIFIED,"zoo.net",341) +
// verify(result,PARENT_SPECIFIED,"zoo.net",347) +
// verify(result,PARENT_SPECIFIED,"zoo.edu",111) ,7);
// Test 6++ BUGBUG needs to be fixed
// ST(7); REINIT;
// br(request, "snow.imac.net:2020");
// FP; RE(verify(result,PARENT_DIRECT,0,0),7);
// Test 6+++ BUGBUG needs to be fixed
// ST(8); REINIT;
// br(request, "snow.imac.net:819");
// URL* u = new URL();
// char* r = "http://snow.imac.net:819/";
// u->create(0);
// u->parse(r,strlen(r));
// u->port_set(819);
// request->hdr->url_set(u);
// ink_assert(request->hdr->url_get()->port_get() == 819);
// printf("url: %s\n",request->hdr->url_get()->string_get(0));
// FP; RE(verify(result,PARENT_SPECIFIED,"genie",80),8);
// Test 7 - N Parent Table
tbl[0] = '\0';
T("dest_domain=rabbit.net parent=fuzzy:80,fluffy:80,furry:80,frisky:80 round_robin=strict go_direct=true\n");
REBUILD;
// Test 8
ST(8);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 7);
params->markParentDown(result, fail_threshold, retry_time);
// Test 9
ST(9);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 8);
// Test 10
ST(10);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 9);
// Test 11
ST(11);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 10);
// restart the loop
// Test 12
ST(12);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 11);
// Test 13
ST(13);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 12);
// Test 14
ST(14);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 13);
// Test 15
ST(15);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 14);
params->markParentDown(result, fail_threshold, retry_time);
// restart the loop
// Test 16
ST(16);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 15);
// Test 17
ST(17);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 16);
// Test 18
ST(18);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 17);
// Test 19
ST(19);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 18);
// restart the loop
// Test 20
ST(20);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 19);
// Test 21
ST(21);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 20);
// Test 22
ST(22);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 21);
params->markParentDown(result, fail_threshold, retry_time);
// Test 23 - 32
for (i = 23; i < 33; i++) {
ST(i);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), i);
}
params->markParentDown(result, 1, 5); // now they're all down
// Test 33 - 132
for (i = 33; i < 133; i++) {
ST(i);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_DIRECT, nullptr, 0), i);
}
// sleep(5); // parents should come back up; they don't
sleep(params->policy.ParentRetryTime + 1);
// Fix: The following tests failed because
// br() should set xact_start correctly instead of 0.
// Test 133 - 172
for (i = 133; i < 173; i++) {
ST(i);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
switch (i % 4) {
case 0:
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), i);
break;
case 1:
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), i);
break;
case 2:
RE(verify(result, PARENT_SPECIFIED, "furry", 80), i);
break;
case 3:
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), i);
break;
default:
ink_assert(0);
}
}
// Test 173
tbl[0] = '\0';
ST(173);
T("dest_domain=rabbit.net parent=fuzzy:80|1.0;fluffy:80|1.0 secondary_parent=furry:80|1.0;frisky:80|1.0 "
"round_robin=consistent_hash go_direct=false\n");
REBUILD;
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 173);
params->markParentDown(result, fail_threshold, retry_time); // fuzzy is down.
// Test 174
ST(174);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 174);
params->markParentDown(result, fail_threshold, retry_time); // frisky is down.
// Test 175
ST(175);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 175);
params->markParentDown(result, fail_threshold, retry_time); // frisky is down.
// Test 176
ST(176);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 176);
params->markParentDown(result, fail_threshold, retry_time); // all are down now.
// Test 177
ST(177);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_FAIL, nullptr, 80), 177);
// Test 178
tbl[0] = '\0';
ST(178);
T("dest_domain=rabbit.net parent=fuzzy:80|1.0;fluffy:80|1.0;furry:80|1.0;frisky:80|1.0 "
"round_robin=latched go_direct=false\n");
REBUILD;
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 178);
params->markParentDown(result, fail_threshold, retry_time); // fuzzy is down
// Test 179
ST(179);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 179);
params->markParentDown(result, fail_threshold, retry_time); // fluffy is down
// Test 180
ST(180);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 180);
params->markParentDown(result, fail_threshold, retry_time); // furry is down
// Test 181
ST(181);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 181);
params->markParentDown(result, fail_threshold, retry_time); // frisky is down and we should be back on fuzzy.
// Test 182
ST(182);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_FAIL, nullptr, 80), 182);
// wait long enough so that fuzzy is retryable.
sleep(params->policy.ParentRetryTime - 2);
// Test 183
ST(183);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 183);
// Test 184
// mark fuzzy down with HostStatus API.
_st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
ST(184);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 184);
// Test 185
// mark fluffy down and expect furry to be chosen
_st.setHostStatus("fluffy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
ST(185);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 185);
// Test 186
// mark furry and frisky down, fuzzy up and expect fuzzy to be chosen
_st.setHostStatus("furry", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
_st.setHostStatus("frisky", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
_st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
ST(186);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 186);
// Test 187
// test the HostStatus API with ParentConsistent Hash.
tbl[0] = '\0';
ST(187);
T("dest_domain=rabbit.net parent=fuzzy:80|1.0;fluffy:80|1.0;furry:80|1.0;frisky:80|1.0 "
"round_robin=consistent_hash go_direct=false\n");
REBUILD;
// mark all up.
_st.setHostStatus("furry", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("fluffy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("frisky", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 187);
// Test 188
// mark fuzzy down and expect fluffy.
_st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
ST(188);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 188);
// Test 189
// mark fuzzy back up and expect fuzzy.
_st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
ST(189);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 189);
// Test 190
// mark fuzzy back down and set the host status down
// then wait for fuzzy to become available.
// even though fuzzy becomes retryable we should not select it
// because the host status is set to down.
params->markParentDown(result, fail_threshold, retry_time);
// set host status down
_st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
// sleep long enough so that fuzzy is retryable
sleep(params->policy.ParentRetryTime + 1);
ST(190);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 190);
// now set the host status on fuzzy to up and it should now
// be retried.
_st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
ST(191);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 191);
// Test 192
tbl[0] = '\0';
ST(192);
T("dest_domain=rabbit.net parent=fuzzy:80,fluffy:80,furry:80,frisky:80 round_robin=false go_direct=true\n");
REBUILD;
// mark all up.
_st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("fluffy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("furry", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
_st.setHostStatus("frisky", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
// fuzzy should be chosen.
sleep(1);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 192);
// Test 193
// mark fuzzy down and wait for it to become retryable
ST(193);
params->markParentDown(result, fail_threshold, retry_time);
sleep(params->policy.ParentRetryTime + 1);
// since the host status is down even though fuzzy is
// retryable, fluffy should be chosen
_st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 193);
// Test 194
// set the host status for fuzzy back up and since its
// retryable fuzzy should be chosen
ST(194);
_st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL);
REINIT;
br(request, "i.am.rabbit.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 194);
// Test 195
// secondary_mode=1 (default) is covered by tests cases 173-177 above
// secondary_mode=2 is tested here
// fuzzy { frisky furry } fluffy
tbl[0] = '\0';
ST(195);
T("dest_domain=rabbit.net parent=fuzzy:80|1.0;fluffy:80|1.0 secondary_parent=furry:80|1.0;frisky:80|1.0 "
"round_robin=consistent_hash go_direct=false secondary_mode=2\n");
REBUILD;
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 195);
params->markParentDown(result, fail_threshold, retry_time); // fuzzy is down.
// Test 196
ST(196);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 196);
params->markParentDown(result, fail_threshold, retry_time); // fluffy is down.
// Test 197
ST(197);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 197);
params->markParentDown(result, fail_threshold, retry_time); // frisky is down.
// Test 198
ST(198);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 198);
params->markParentDown(result, fail_threshold, retry_time); // all are down now.
// Test 199
ST(199);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_FAIL, nullptr, 80), 199);
// Test 200
// secondary_mode=3 is tested here first-choice NOT marked down
// fuzzy { frisky furry } fluffy
tbl[0] = '\0';
ST(200);
T("dest_domain=rabbit.net parent=fuzzy:80|1.0;fluffy:80|1.0 secondary_parent=furry:80|1.0;frisky:80|1.0 "
"round_robin=consistent_hash go_direct=false secondary_mode=3\n");
REBUILD;
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fuzzy", 80), 200);
params->markParentDown(result, fail_threshold, retry_time); // fuzzy is down.
// Test 201
ST(201);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 201);
params->markParentDown(result, fail_threshold, retry_time); // fluffy is down.
// Test 202
ST(202);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 202);
params->markParentDown(result, fail_threshold, retry_time); // frisky is down.
// Test 203
ST(203);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 203);
params->markParentDown(result, fail_threshold, retry_time); // all are down now.
// Test 204
ST(204);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_FAIL, nullptr, 80), 204);
// Test 205
// secondary_mode=3 is tested here first-choice marked down
// fuzzy { frisky furry } fluffy
tbl[0] = '\0';
ST(205);
T("dest_domain=rabbit.net parent=fuzzy:80|1.0;fluffy:80|1.0 secondary_parent=furry:80|1.0;frisky:80|1.0 "
"round_robin=consistent_hash go_direct=false secondary_mode=3\n");
REBUILD;
_st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "frisky", 80), 205);
params->markParentDown(result, fail_threshold, retry_time); // frisky is down.
// Test 206
ST(206);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "furry", 80), 206);
params->markParentDown(result, fail_threshold, retry_time); // furry is down.
// Test 207
ST(207);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_SPECIFIED, "fluffy", 80), 207);
params->markParentDown(result, fail_threshold, retry_time); // all are down now.
// Test 208
ST(208);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_FAIL, nullptr, 80), 208);
// Tests 209 through 211 test that host selection is based upon the hash_string
// Test 209
// fuzzy { curly larry, moe } fluffy
tbl[0] = '\0';
ST(209);
T("dest_domain=stooges.net parent=curly:80|1.0&myhash;joe:80|1.0&hishash;larry:80|1.0&ourhash "
"round_robin=consistent_hash go_direct=false\n");
REBUILD;
REINIT;
br(request, "i.am.stooges.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "larry", 80), 209);
// Test 210
// fuzzy { curly larry, moe } fluffy
tbl[0] = '\0';
ST(210);
T("dest_domain=stooges.net parent=curly:80|1.0&ourhash;joe:80|1.0&hishash;larry:80|1.0&myhash "
"round_robin=consistent_hash go_direct=false\n");
REBUILD;
REINIT;
br(request, "i.am.stooges.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "curly", 80), 210);
// Test 211
// fuzzy { curly larry, moe } fluffy
tbl[0] = '\0';
ST(211);
T("dest_domain=stooges.net parent=curly:80|1.0&ourhash;joe:80|1.0&hishash;larry:80|1.0&myhash "
"secondary_parent=carol:80|1.0&ourhash;betty:80|1.0&hishash;donna:80|1.0&myhash "
"round_robin=consistent_hash go_direct=false\n");
REBUILD;
REINIT;
_st.setHostStatus("curly", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL);
br(request, "i.am.stooges.net");
FP;
RE(verify(result, PARENT_SPECIFIED, "carol", 80), 211);
// Test 212
tbl[0] = '\0';
ST(212);
T("dest_domain=mouse.com parent=mickey:80|0.33;minnie:80|0.33;goofy:80|0.33 "
"round_robin=consistent_hash go_direct=false\n");
REBUILD;
REINIT;
br(request, "i.am.mouse.com");
FP;
RE(verify(result, PARENT_SPECIFIED, "goofy", 80), 212);
// Test 213
// markdown goofy and minnie gets chosen.
ST(213);
params->markParentDown(result, fail_threshold, retry_time); // marked down goofy
REINIT;
br(request, "i.am.mouse.com");
FP;
RE(verify(result, PARENT_SPECIFIED, "minnie", 80), 213);
delete request;
delete result;
delete params;
printf("Tests Passed: %d\nTests Failed: %d\n", passes, fails);
*pstatus = (!fails ? REGRESSION_TEST_PASSED : REGRESSION_TEST_FAILED);
}
// verify returns 1 iff the test passes
int
verify(ParentResult *r, ParentResultType e, const char *h, int p)
{
if (is_debug_tag_set("parent_select")) {
show_result(r);
}
return (r->result != e) ? 0 : ((e != PARENT_SPECIFIED) ? 1 : (strcmp(r->hostname, h) ? 0 : ((r->port == p) ? 1 : 0)));
}
// br creates an HttpRequestData object
void
br(HttpRequestData *h, const char *os_hostname, sockaddr const *dest_ip)
{
h->hdr = new HTTPHdr();
h->hdr->create(HTTP_TYPE_REQUEST);
h->hostname_str = ats_strdup(os_hostname);
h->xact_start = time(nullptr);
ink_zero(h->src_ip);
ink_zero(h->dest_ip);
ats_ip_copy(&h->dest_ip.sa, dest_ip);
h->incoming_port = 80;
h->api_info = new HttpApiInfo();
}
// show_result prints out the ParentResult information
void
show_result(ParentResult *p)
{
switch (p->result) {
case PARENT_UNDEFINED:
printf("result is PARENT_UNDEFINED\n");
break;
case PARENT_DIRECT:
printf("result is PARENT_DIRECT\n");
break;
case PARENT_SPECIFIED:
printf("result is PARENT_SPECIFIED\n");
printf("hostname is %s\n", p->hostname);
printf("port is %d\n", p->port);
break;
case PARENT_FAIL:
printf("result is PARENT_FAIL\n");
break;
default:
// Handled here:
// PARENT_AGENT
break;
}
}