Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1344 lines (1196 sloc) 41 KB
/** @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 "Main.h"
#include "Error.h"
#include "ProxyConfig.h"
#include "HTTP.h"
#include "HttpTransact.h"
#define MAX_SIMPLE_RETRIES 5
#define MAX_UNAVAILABLE_SERVER_RETRIES 5
typedef ControlMatcher<ParentRecord, ParentResult> P_table;
// Global Vars for Parent Selection
static const char modulePrefix[] = "[ParentSelection]";
static ConfigUpdateHandler<ParentConfig> *parentConfigUpdate = NULL;
// 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 *enable_var = "proxy.config.http.parent_proxy_routing_enable";
static const char *threshold_var = "proxy.config.http.parent_proxy.fail_threshold";
static const char *ParentResultStr[] = {"PARENT_UNDEFINED", "PARENT_DIRECT", "PARENT_SPECIFIED", "PARENT_AGENT", "PARENT_FAIL"};
//
// 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()
{
bool enable = false;
int32_t retry_time = 0;
int32_t fail_threshold = 0;
// Handle parent timeout
REC_ReadConfigInteger(retry_time, retry_var);
ParentRetryTime = retry_time;
// Handle parent enable
REC_ReadConfigInteger(enable, enable_var);
ParentEnable = enable;
// Handle the fail threshold
REC_ReadConfigInteger(fail_threshold, threshold_var);
FailThreshold = fail_threshold;
}
ParentConfigParams::ParentConfigParams(P_table *_parent_table) : parent_table(_parent_table), DefaultParent(NULL), policy()
{
char *default_val = NULL;
// Handle default parent
REC_ReadConfigStringAlloc(default_val, default_var);
DefaultParent = createDefaultParent(default_val);
ats_free(default_val);
}
bool
ParentConfigParams::apiParentExists(HttpRequestData *rdata)
{
return (rdata->api_info && rdata->api_info->parent_proxy_name != NULL && rdata->api_info->parent_proxy_port > 0);
}
void
ParentConfigParams::findParent(HttpRequestData *rdata, ParentResult *result)
{
P_table *tablePtr = parent_table;
ParentRecord *defaultPtr = DefaultParent;
ParentRecord *rec;
Debug("parent_select", "In ParentConfigParams::findParent(): parent_table: %p.", parent_table);
ink_assert(result->result == PARENT_UNDEFINED);
// Check to see if we are enabled
Debug("parent_select", "policy.ParentEnable: %d", policy.ParentEnable);
if (policy.ParentEnable == 0) {
result->result = PARENT_DIRECT;
return;
}
// Initialize the result structure
result->reset();
// 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;
Debug("parent_select", "Result for %s was API set parent %s:%d", rdata->get_host(), result->hostname, result->port);
}
tablePtr->Match(rdata, result);
rec = result->rec;
if (rec == NULL) {
// No parents were found
//
// If there is a default parent, use it
if (defaultPtr != NULL) {
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);
}
const char *host = rdata->get_host();
switch (result->result) {
case PARENT_UNDEFINED:
Debug("parent_select", "PARENT_UNDEFINED");
Debug("parent_select", "Result for %s was %s", host, 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", host, ParentResultStr[result->result]);
break;
case PARENT_SPECIFIED:
Debug("parent_select", "PARENT_SPECIFIED");
Debug("parent_select", "Result for %s was parent %s:%d", host, result->hostname, result->port);
break;
default:
// Handled here:
// PARENT_AGENT
break;
}
}
void
ParentConfigParams::nextParent(HttpRequestData *rdata, ParentResult *result)
{
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);
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)
{
ParentResult result;
findParent(rdata, &result);
if (result.result == PARENT_SPECIFIED) {
return true;
} else {
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);
// Enable
parentConfigUpdate->attach(enable_var);
// Fail Threshold
parentConfigUpdate->attach(threshold_var);
}
void
ParentConfig::reconfigure()
{
ParentConfigParams *params = NULL;
// Allocate parent table
P_table *pTable = new P_table(file_var, modulePrefix, &http_dest_tags);
params = new ParentConfigParams(pTable);
ink_assert(params != NULL);
m_id = configProcessor.set(m_id, params);
if (is_debug_tag_set("parent_config")) {
ParentConfig::print();
}
}
// void ParentConfig::print
//
// Debugging function
//
void
ParentConfig::print()
{
ParentConfigParams *params = ParentConfig::acquire();
printf("Parent Selection Config\n");
printf("\tEnabled %d\tRetryTime %d\n", params->policy.ParentEnable, params->policy.ParentRetryTime);
if (params->DefaultParent == NULL) {
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 == NULL) {
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 > 500 && c < 600) {
codes.push_back(HTTP_STATUS_SERVICE_UNAVAILABLE);
}
}
for (int i = 0; i < numTok; i++) {
c = atoi(pTok[i]);
if (c > 500 && c < 600) {
TSDebug("parent_select", "loading response code: %d", c);
codes.push_back(c);
}
}
std::sort(codes.begin(), codes.end());
}
// 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 = NULL;
int port = 0;
char *tmp = NULL, *tmp2 = NULL;
const char *errPtr = NULL;
float weight = 1.0;
if (parents != NULL && isPrimary == true) {
return "Can not specify more than one set of parents";
}
if (secondary_parents != NULL && 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 = (pRecord *)ats_malloc(sizeof(pRecord) * numTok);
} else {
this->secondary_parents = (pRecord *)ats_malloc(sizeof(pRecord) * numTok);
}
// Loop through the set of parents specified
//
for (int i = 0; i < numTok; i++) {
current = pTok[i];
// Find the parent port
tmp = (char *)strchr(current, ':');
if (tmp == NULL) {
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 = (char *)strchr(current, '|');
if (tmp2) {
if (sscanf(tmp2 + 1, "%f", &weight) != 1) {
errPtr = "Malformed parent weight";
goto MERROR;
}
}
// Make sure that is no garbage beyond the parent
// port or weight
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 emtpy";
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;
} 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 (isPrimary) {
num_parents = numTok;
} else {
num_secondary_parents = numTok;
}
return NULL;
MERROR:
ats_free(parents);
parents = NULL;
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 = NULL;
this->parent_is_proxy = true;
errPtr = ProcessParents(val, true);
if (errPtr != NULL) {
errBuf = (char *)ats_malloc(1024);
snprintf(errBuf, 1024, "%s %s for default parent proxy", modulePrefix, errPtr);
SignalError(errBuf, alarmAlready);
ats_free(errBuf);
return false;
} else {
return true;
}
}
// config_parse_error 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()
//
config_parse_error
ParentRecord::Init(matcher_line *line_info)
{
const char *errPtr = NULL;
const char *tmp;
char *label;
char *val;
bool used = false;
ParentRR_t round_robin = P_NO_ROUND_ROBIN;
char buf[128];
this->line_num = line_info->line_num;
this->scheme = NULL;
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 == NULL) {
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 {
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) {
errPtr = ProcessParents(val, true);
used = true;
} else if (strcasecmp(label, "secondary_parent") == 0) {
errPtr = ProcessParents(val, 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 == NULL) {
unavailable_server_retry_responses = new UnavailableServerResponseCodes(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;
}
}
// Report errors generated by ProcessParents();
if (errPtr != NULL) {
return config_parse_error("%s %s at line %d", modulePrefix, errPtr, line_num);
}
if (used == true) {
// Consume the label/value pair we used
line_info->line[0][i] = NULL;
line_info->num_el--;
}
}
// parent_retry may only be enabled if the parents are origin servers, parent_is_proxy is false.
if (parent_is_proxy == true) {
if (parent_retry > 0) {
Warning("%s disabling parent_retry on line %d because parent_is_proxy is true", modulePrefix, line_num);
parent_retry = PARENT_RETRY_NONE;
if (unavailable_server_retry_responses != NULL) {
delete unavailable_server_retry_responses;
unavailable_server_retry_responses = NULL;
}
}
} else {
// delete unavailable_server_retry_responses if unavailable_server_retry is not enabled.
if (unavailable_server_retry_responses != NULL && !(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 = NULL;
} else if (unavailable_server_retry_responses == NULL && (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(NULL);
}
}
if (this->parents == NULL && go_direct == false) {
return config_parse_error("%s No parent specified in parent.config at line %d", modulePrefix, line_num);
}
// Process any modifiers to the directive, if they exist
if (line_info->num_el > 0) {
tmp = ProcessModifiers(line_info);
if (tmp != NULL) {
return config_parse_error("%s %s at line %d in parent.config", modulePrefix, tmp, line_num);
}
// record SCHEME modifier if present.
// NULL if not present
this->scheme = this->getSchemeModText();
if (this->scheme != NULL) {
// 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:
TSDebug("parent_select", "allocating ParentRoundRobin() lookup strategy.");
selection_strategy = new ParentRoundRobin(this, round_robin);
break;
case P_CONSISTENT_HASH:
TSDebug("parent_select", "allocating ParentConsistentHash() lookup strategy.");
selection_strategy = new ParentConsistentHash(this);
break;
default:
ink_release_assert(0);
}
return config_parse_error::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()
{
printf("\t\t");
for (int i = 0; i < num_parents; i++) {
printf(" %s:%d ", parents[i].hostname, parents[i].port);
}
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 == NULL || *val == '\0') {
return NULL;
}
newRec = new ParentRecord;
if (newRec->DefaultInit(val) == true) {
return newRec;
} else {
delete newRec;
return NULL;
}
}
//
// 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()
{
char *default_val = NULL;
int retry_time = 30;
int fail_threshold;
ParentConfigParams *params = NULL;
// 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 != NULL);
// 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 parent enable
// enable is always true for use. We will come here only if socks is enabled
params->policy.ParentEnable = 1;
// 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();
}
}
void
SocksServerConfig::print()
{
ParentConfigParams *params = SocksServerConfig::acquire();
printf("Parent Selection Config for Socks Server\n");
printf("\tEnabled %d\tRetryTime %d\n", params->policy.ParentEnable, params->policy.ParentRetryTime);
if (params->DefaultParent == NULL) {
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 = NULL;
ParentConfigParams *params = NULL;
passes = fails = 0;
config.startup();
char tbl[2048];
HttpRequestData *request = NULL;
ParentResult *result = NULL;
#define T(x) \
do { \
ink_strlcat(tbl, x, sizeof(tbl)); \
} while (0)
#define REBUILD \
do { \
delete ParentTable; \
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); \
params = new ParentConfigParams(ParentTable); \
params->policy.FailThreshold = 1; \
params->policy.ParentEnable = true; \
params->policy.ParentRetryTime = 5; \
} while (0)
#define REINIT \
do { \
if (request != NULL) { \
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); \
} while (0)
// Test 1
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);
// 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);
// 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);
// 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); // 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, 0, 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); // 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); // 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); // 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); // all are down now.
// Test 177
ST(177);
REINIT;
br(request, "i.am.rabbit.net");
FP;
sleep(1);
RE(verify(result, PARENT_FAIL, NULL, 80), 177);
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 = (char *)ats_strdup(os_hostname);
h->xact_start = time(NULL);
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;
}
}