diff --git a/doc/admin-guide/files/parent.config.en.rst b/doc/admin-guide/files/parent.config.en.rst index 9578713c062..3bde6bfa58d 100644 --- a/doc/admin-guide/files/parent.config.en.rst +++ b/doc/admin-guide/files/parent.config.en.rst @@ -129,6 +129,17 @@ The following list shows the possible actions and their allowed values. origin server. You can specify either a hostname or an IP address, but; you must specify the port number. +.. _parent-config-format-secondary_parent-parent: + +``secondary_parent`` + An optional ordered list of secondary parent servers using the same format + as the ``parent`` list. A ``secondary_parent`` list only applies + when ``round_robin`` is set to ``consistent_hash``. When using + ``consistent_hash``, if the server chosen from the primary list fails, + a parent is selected from a secondary consistent hash ring. This feature + works best in a multi-tiered cache hierarchy where one might take advantage + of the content affinint built up on a secondary list of parents. + .. _parent-config-format-round-robin: ``round_robin`` @@ -145,7 +156,24 @@ The following list shows the possible actions and their allowed values. would go to the down parent is rehashed amongst the remaining parents. The other traffic is unaffected. Once the downed parent becomes available, the traffic distribution returns to the pre-down - state. + state. If a ``secondary_parent`` list is used, the url hashed against + a parent in the ``secondary_parent`` list. Use this to take advantage + of content affinity built up in a secondary group of parents. + +.. _parent-config-format-parent_is_proxy: + +``parent_is_proxy`` + One of the following values: + + - ``true`` - Specifies that the parents are ATS cache proxies, within + a hierarchy. This is the default value, if ``parent_is_proxy`` + is not specified in the configuration. + + - ``false`` - Specifies that the parents are origin servers. The request + url's are modified so they are appropriate for origin + requests. Normal Parent Selection behaviour applies to + the origins listed. Since these would be a list of origin + servers, set go_direct described below to ``false``. .. _parent-config-format-go-direct: diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index f59658a58d5..ab285522477 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -982,6 +982,26 @@ Parent Proxy Configuration Don't try to resolve DNS, forward all DNS requests to the parent. This is off (``0``) by default. +.. ts:cv:: CONFIG proxy.config.http.parent_origin.simple_retry_enabled INT 0 + + Enable the simple retry feature, This is off (``0``) by default. simple retry is only used for + parent origin servers, see configuration information for parent.config. + +.. ts:cv:: CONFIG proxy.config.http.parent_origin.simple_retry_response_codes STRING 0 + + This is a comma separated list of response codes that will trigger a simple retry on a parent + origin server if ``simple_retry`` above is enabled. This is a ``404`` by default. + +.. ts:cv:: CONFIG proxy.config.http.parent_origin.dead_server_retry_enabled INT 0 + + Enable the dead_server retry feature, This is off (``0``) by default. dead server retry + is only used for parent origin servers, see configuration information for parent.config. + +.. ts:cv:: CONFIG proxy.config.http.parent_origin.dead_server_retry_response_codes STRING 0 + + This is a comma separated list of response codes that will trigger a dead server retry on + a parent origin server if ``dead_server_retry`` above is enabled. This is a ``503`` by default. + HTTP Connection Timeouts ======================== diff --git a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst index 298124db89c..c8ccd59f8a2 100644 --- a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst +++ b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst @@ -140,6 +140,12 @@ The following configurations (from ``records.config``) are overridable. | :ts:cv:`proxy.config.http.cache.range.write` | :ts:cv:`proxy.config.http.global_user_agent_header` | :ts:cv:`proxy.config.http.slow.log.threshold` +| :ts:cv:`proxy.config.http.parent_proxy.per_parent_connect_attempts` +| :ts:cv:`proxy.config.http.parent_proxy.total_connect_attempts` +| :ts:cv:`proxy.config.http.parent_origin.simple_retry_enabled` +| :ts:cv:`proxy.config.http.parent_origin.simple_retry_response_codes` +| :ts:cv:`proxy.config.http.parent_origin.dead_server_retry_enabled` +| :ts:cv:`proxy.config.http.parent_origin.dead_server_retry_response_codes` Examples ======== diff --git a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst index abe104551b8..446fa42bee3 100644 --- a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst +++ b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst @@ -205,6 +205,18 @@ Enumeration Members .. c:member:: TSOverridableConfigKey TS_CONFIG_HTTP_GLOBAL_USER_AGENT_HEADER +.. c:member:: TSOverridableConfigKey TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS + +.. c:member:: TSOverridableConfigKey TS_CONFIG_HTTP_PARENT_TOTAL_CONNECT_ATTEMPTS + +.. c:member:: TSOverridableConfigKey TS_CONFIG_HTTP_SIMPLE_RETRY_ENABLED + +.. c:member:: TSOverridableConfigKey TS_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES + +.. c:member:: TSOverridableConfigKey TS_CONFIG_HTTP_DEAD_SERVER_RETRY_ENABLED + +.. c:member:: TSOverridableConfigKey TS_CONFIG_HTTP_DEAD_SERVER_RETRY_RESPONSE_CODES + .. c:member:: TSOverridableConfigKey TS_CONFIG_LAST_ENTRY Description diff --git a/lib/ts/ConsistentHash.cc b/lib/ts/ConsistentHash.cc index 461c39fe530..912bc74e501 100644 --- a/lib/ts/ConsistentHash.cc +++ b/lib/ts/ConsistentHash.cc @@ -19,7 +19,7 @@ limitations under the License. */ -#include "ts/ConsistentHash.h" +#include "ConsistentHash.h" #include #include #include @@ -67,13 +67,19 @@ ATSConsistentHash::insert(ATSConsistentHashNode *node, float weight, ATSHash64 * } ATSConsistentHashNode * -ATSConsistentHash::lookup(const char *url, ATSConsistentHashIter *i, bool *w, ATSHash64 *h) +ATSConsistentHash::lookup(const char *url, size_t url_len, ATSConsistentHashIter *i, bool *w, ATSHash64 *h) { uint64_t url_hash; ATSConsistentHashIter NodeMapIterUp, *iter; ATSHash64 *thash; bool *wptr, wrapped = false; + if (url_len <= 0 && url) { + url_len = strlen(url); + } else { + url_len = 0; + } + if (h) { thash = h; } else if (hash) { @@ -95,7 +101,7 @@ ATSConsistentHash::lookup(const char *url, ATSConsistentHashIter *i, bool *w, AT } if (url) { - thash->update(url, strlen(url)); + thash->update(url, url_len); thash->final(); url_hash = thash->get(); thash->clear(); @@ -124,13 +130,19 @@ ATSConsistentHash::lookup(const char *url, ATSConsistentHashIter *i, bool *w, AT } ATSConsistentHashNode * -ATSConsistentHash::lookup_available(const char *url, ATSConsistentHashIter *i, bool *w, ATSHash64 *h) +ATSConsistentHash::lookup_available(const char *url, size_t url_len, ATSConsistentHashIter *i, bool *w, ATSHash64 *h) { uint64_t url_hash; ATSConsistentHashIter NodeMapIterUp, *iter; ATSHash64 *thash; bool *wptr, wrapped = false; + if (url_len <= 0 && url) { + url_len = strlen(url); + } else { + url_len = 0; + } + if (h) { thash = h; } else if (hash) { @@ -152,7 +164,7 @@ ATSConsistentHash::lookup_available(const char *url, ATSConsistentHashIter *i, b } if (url) { - thash->update(url, strlen(url)); + thash->update(url, url_len); thash->final(); url_hash = thash->get(); thash->clear(); @@ -179,6 +191,34 @@ ATSConsistentHash::lookup_available(const char *url, ATSConsistentHashIter *i, b return (*iter)->second; } +ATSConsistentHashNode * +ATSConsistentHash::lookup_by_hashval(uint64_t hashval, ATSConsistentHashIter *i, bool *w) +{ + ATSConsistentHashIter NodeMapIterUp, *iter; + bool *wptr, wrapped = false; + + if (w) { + wptr = w; + } else { + wptr = &wrapped; + } + + if (i) { + iter = i; + } else { + iter = &NodeMapIterUp; + } + + *iter = NodeMap.lower_bound(hashval); + + if (*iter == NodeMap.end()) { + *wptr = true; + *iter = NodeMap.begin(); + } + + return (*iter)->second; +} + ATSConsistentHash::~ATSConsistentHash() { if (hash) { diff --git a/lib/ts/ConsistentHash.h b/lib/ts/ConsistentHash.h index 1440b2349ee..49822ad9d4f 100644 --- a/lib/ts/ConsistentHash.h +++ b/lib/ts/ConsistentHash.h @@ -22,7 +22,7 @@ #ifndef __CONSISTENT_HASH_H__ #define __CONSISTENT_HASH_H__ -#include "ts/Hash.h" +#include "Hash.h" #include #include #include @@ -49,9 +49,11 @@ typedef std::map::iterator ATSConsistentHashI struct ATSConsistentHash { ATSConsistentHash(int r = 1024, ATSHash64 *h = NULL); void insert(ATSConsistentHashNode *node, float weight = 1.0, ATSHash64 *h = NULL); - ATSConsistentHashNode *lookup(const char *url = NULL, ATSConsistentHashIter *i = NULL, bool *w = NULL, ATSHash64 *h = NULL); - ATSConsistentHashNode *lookup_available(const char *url = NULL, ATSConsistentHashIter *i = NULL, bool *w = NULL, - ATSHash64 *h = NULL); + ATSConsistentHashNode *lookup(const char *url = NULL, size_t url_len = 0, ATSConsistentHashIter *i = NULL, bool *w = NULL, + ATSHash64 *h = NULL); + ATSConsistentHashNode *lookup_available(const char *url = NULL, size_t url_len = 0, ATSConsistentHashIter *i = NULL, + bool *w = NULL, ATSHash64 *h = NULL); + ATSConsistentHashNode *lookup_by_hashval(uint64_t hashval, ATSConsistentHashIter *i = NULL, bool *w = NULL); ~ATSConsistentHash(); private: diff --git a/lib/ts/apidefs.h.in b/lib/ts/apidefs.h.in index e8acced00f1..74a01910674 100644 --- a/lib/ts/apidefs.h.in +++ b/lib/ts/apidefs.h.in @@ -693,6 +693,12 @@ typedef enum { TS_CONFIG_HTTP_NUMBER_OF_REDIRECTIONS, TS_CONFIG_HTTP_CACHE_MAX_OPEN_WRITE_RETRIES, TS_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY, + TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS, + TS_CONFIG_HTTP_PARENT_TOTAL_CONNECT_ATTEMPTS, + TS_CONFIG_HTTP_SIMPLE_RETRY_ENABLED, + TS_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES, + TS_CONFIG_HTTP_DEAD_SERVER_RETRY_ENABLED, + TS_CONFIG_HTTP_DEAD_SERVER_RETRY_RESPONSE_CODES, TS_CONFIG_LAST_ENTRY } TSOverridableConfigKey; diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc index dc60715a8e8..b83fa0943e5 100644 --- a/mgmt/RecordsConfig.cc +++ b/mgmt/RecordsConfig.cc @@ -519,6 +519,17 @@ static const RecordElement RecordsConfig[] = {RECT_CONFIG, "proxy.config.http.forward.proxy_auth_to_parent", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_NULL, NULL, RECA_NULL} , + // ################################### + // # parent origin configuration # + // ################################### + {RECT_CONFIG, "proxy.config.http.parent_origin.simple_retry_enabled", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http.parent_origin.simple_retry_response_codes", RECD_STRING, "404", RECU_DYNAMIC, RR_NULL, RECC_STR, "^([0-9]+,)$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http.parent_origin.dead_server_retry_enabled", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http.parent_origin.dead_server_retry_response_codes", RECD_STRING, "503", RECU_DYNAMIC, RR_NULL, RECC_STR, "^([0-9]+,)$", RECA_NULL} + , // ################################### // # NO DNS DOC IN CACHE # // ################################### diff --git a/plugins/experimental/ts_lua/ts_lua_http_config.c b/plugins/experimental/ts_lua/ts_lua_http_config.c index 05edf210eef..33dfb227d75 100644 --- a/plugins/experimental/ts_lua/ts_lua_http_config.c +++ b/plugins/experimental/ts_lua/ts_lua_http_config.c @@ -113,6 +113,12 @@ typedef enum { TS_LUA_CONFIG_HTTP_NUMBER_OF_REDIRECTIONS = TS_CONFIG_HTTP_NUMBER_OF_REDIRECTIONS, TS_LUA_CONFIG_HTTP_CACHE_MAX_OPEN_WRITE_RETRIES = TS_CONFIG_HTTP_CACHE_MAX_OPEN_WRITE_RETRIES, TS_LUA_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY = TS_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY, + TS_LUA_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS = TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS, + TS_LUA_CONFIG_HTTP_PARENT_TOTAL_CONNECT_ATTEMPTS = TS_CONFIG_HTTP_PARENT_TOTAL_CONNECT_ATTEMPTS, + TS_LUA_CONFIG_HTTP_SIMPLE_RETRY_ENABLED = TS_CONFIG_HTTP_SIMPLE_RETRY_ENABLED, + TS_LUA_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES = TS_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES, + TS_LUA_CONFIG_HTTP_DEAD_SERVER_RETRY_ENABLED = TS_CONFIG_HTTP_DEAD_SERVER_RETRY_ENABLED, + TS_LUA_CONFIG_HTTP_DEAD_SERVER_RETRY_RESPONSE_CODES = TS_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES, TS_LUA_CONFIG_LAST_ENTRY = TS_CONFIG_LAST_ENTRY, } TSLuaOverridableConfigKey; @@ -195,8 +201,11 @@ ts_lua_var_item ts_lua_http_config_vars[] = { TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CACHE_GENERATION), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_BODY_FACTORY_TEMPLATE_BASE), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CACHE_OPEN_WRITE_FAIL_ACTION), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_ENABLE_REDIRECTION), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_NUMBER_OF_REDIRECTIONS), - TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CACHE_MAX_OPEN_WRITE_RETRIES), - TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_LAST_ENTRY), + TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CACHE_MAX_OPEN_WRITE_RETRIES), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY), + TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PARENT_TOTAL_CONNECT_ATTEMPTS), + TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_SIMPLE_RETRY_ENABLED), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES), + TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_DEAD_SERVER_RETRY_ENABLED), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_DEAD_SERVER_RETRY_RESPONSE_CODES), + TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_LAST_ENTRY), }; // Needed to make sure we have the latest list of overridable http config vars when compiling diff --git a/proxy/InkAPI.cc b/proxy/InkAPI.cc index 8373e1677ac..aa2683c81a0 100644 --- a/proxy/InkAPI.cc +++ b/proxy/InkAPI.cc @@ -7980,6 +7980,30 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr typ = OVERRIDABLE_TYPE_INT; ret = &overridableHttpConfig->redirect_use_orig_cache_key; break; + case TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS: + typ = OVERRIDABLE_TYPE_INT; + ret = &overridableHttpConfig->per_parent_connect_attempts; + break; + case TS_CONFIG_HTTP_PARENT_TOTAL_CONNECT_ATTEMPTS: + typ = OVERRIDABLE_TYPE_INT; + ret = &overridableHttpConfig->parent_connect_attempts; + break; + case TS_CONFIG_HTTP_SIMPLE_RETRY_ENABLED: + typ = OVERRIDABLE_TYPE_INT; + ret = &overridableHttpConfig->simple_retry_enabled; + break; + case TS_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES: + ret = &overridableHttpConfig->simple_retry_response_codes_string; + typ = OVERRIDABLE_TYPE_STRING; + break; + case TS_CONFIG_HTTP_DEAD_SERVER_RETRY_ENABLED: + typ = OVERRIDABLE_TYPE_INT; + ret = &overridableHttpConfig->dead_server_retry_enabled; + break; + case TS_CONFIG_HTTP_DEAD_SERVER_RETRY_RESPONSE_CODES: + ret = &overridableHttpConfig->dead_server_retry_response_codes_string; + typ = OVERRIDABLE_TYPE_STRING; + break; // This helps avoiding compiler warnings, yet detect unhandled enum members. case TS_CONFIG_NULL: case TS_CONFIG_LAST_ENTRY: @@ -8134,6 +8158,20 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char s->t_state.txn_conf->body_factory_template_base_len = 0; } break; + case TS_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES: + if (value && length > 0) { + s->t_state.txn_conf->simple_retry_response_codes_string = const_cast(value); // The "core" likes non-const char* + } else { + s->t_state.txn_conf->simple_retry_response_codes_string = NULL; + } + break; + case TS_CONFIG_HTTP_DEAD_SERVER_RETRY_RESPONSE_CODES: + if (value && length > 0) { + s->t_state.txn_conf->dead_server_retry_response_codes_string = const_cast(value); // The "core" likes non-const char* + } else { + s->t_state.txn_conf->dead_server_retry_response_codes_string = NULL; + } + break; default: return TS_ERROR; break; @@ -8587,6 +8625,10 @@ TSHttpTxnConfigFind(const char *name, int length, TSOverridableConfigKey *conf, if (!strncmp(name, "proxy.config.http.cache.cache_urls_that_look_dynamic", length)) cnf = TS_CONFIG_HTTP_CACHE_CACHE_URLS_THAT_LOOK_DYNAMIC; break; + case 'd': + if (!strncmp(name, "proxy.config.http.parent_origin.simple_retry_enabled", length)) + cnf = TS_CONFIG_HTTP_SIMPLE_RETRY_ENABLED; + break; case 'n': if (!strncmp(name, "proxy.config.http.transaction_no_activity_timeout_in", length)) cnf = TS_CONFIG_HTTP_TRANSACTION_NO_ACTIVITY_TIMEOUT_IN; @@ -8613,9 +8655,37 @@ TSHttpTxnConfigFind(const char *name, int length, TSOverridableConfigKey *conf, } break; + case 57: + if (!strncmp(name, "proxy.config.http.parent_origin.dead_server_retry_enabled", length)) { + cnf = TS_CONFIG_HTTP_DEAD_SERVER_RETRY_ENABLED; + } + break; + case 58: - if (!strncmp(name, "proxy.config.http.connect_attempts_max_retries_dead_server", length)) - cnf = TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DEAD_SERVER; + switch (name[length - 1]) { + case 'r': + if (!strncmp(name, "proxy.config.http.connect_attempts_max_retries_dead_server", length)) + cnf = TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DEAD_SERVER; + break; + case 's': + if (!strncmp(name, "proxy.config.http.parent_proxy.per_parent_connect_attempts", length)) + cnf = TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS; + break; + } + break; + + case 59: + if (!strncmp(name, "proxy.config.http.parent_origin.simple_retry_response_codes", length)) { + cnf = TS_CONFIG_HTTP_SIMPLE_RETRY_RESPONSE_CODES; + typ = TS_RECORDDATATYPE_STRING; + } + break; + + case 64: + if (!strncmp(name, "proxy.config.http.parent_origin.dead_server_retry_response_codes", length)) { + cnf = TS_CONFIG_HTTP_DEAD_SERVER_RETRY_RESPONSE_CODES; + typ = TS_RECORDDATATYPE_STRING; + } break; } diff --git a/proxy/Makefile.am b/proxy/Makefile.am index 01602946c3e..fde3f55fc6e 100644 --- a/proxy/Makefile.am +++ b/proxy/Makefile.am @@ -164,6 +164,10 @@ traffic_server_SOURCES = \ Main.h \ ParentSelection.cc \ ParentSelection.h \ + ParentConsistentHash.cc \ + ParentConsistentHash.h \ + ParentRoundRobin.cc \ + ParentRoundRobin.h \ Plugin.cc \ Plugin.h \ PluginVC.cc \ @@ -277,6 +281,8 @@ traffic_sac_SOURCES = \ ICPStats.cc \ IPAllow.cc \ ParentSelection.cc \ + ParentConsistentHash.cc \ + ParentRoundRobin.cc \ ControlBase.cc \ ControlMatcher.cc \ CacheControl.cc \ diff --git a/proxy/ParentConsistentHash.cc b/proxy/ParentConsistentHash.cc new file mode 100644 index 00000000000..9dc33260827 --- /dev/null +++ b/proxy/ParentConsistentHash.cc @@ -0,0 +1,338 @@ +/** @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 "ParentConsistentHash.h" + +ParentConsistentHash::ParentConsistentHash(ParentRecord *_parent_record) +{ + int i; + + parent_record = _parent_record; + ink_assert(parent_record->num_parents > 0); + parents[PRIMARY] = parent_record->parents; + parents[SECONDARY] = parent_record->secondary_parents; + memset(foundParents, 0, sizeof(foundParents)); + + chash[PRIMARY] = new ATSConsistentHash(); + + for (i = 0; i < parent_record->num_parents; i++) { + chash[PRIMARY]->insert(&(parent_record->parents[i]), parent_record->parents[i].weight, (ATSHash64 *)&hash[PRIMARY]); + } + + if (parent_record->num_secondary_parents > 0) { + Debug("parent_select", "ParentConsistentHash(): initializing the secondary parents hash."); + chash[SECONDARY] = new ATSConsistentHash(); + + for (i = 0; i < parent_record->num_secondary_parents; i++) { + chash[SECONDARY]->insert(&(parent_record->secondary_parents[i]), parent_record->secondary_parents[i].weight, + (ATSHash64 *)&hash[SECONDARY]); + } + } else { + chash[SECONDARY] = NULL; + } + Debug("parent_select", "Using a consistent hash parent selection strategy."); +} + +ParentConsistentHash::~ParentConsistentHash() +{ + if (parent_record) { + delete parent_record; + } +} + + +uint64_t +ParentConsistentHash::getPathHash(HttpRequestData *hrdata, ATSHash64 *h) +{ + const char *tmp = NULL; + int len; + URL *url = hrdata->hdr->url_get(); + + // Always hash on '/' because paths returned by ATS are always stripped of it + h->update("/", 1); + + tmp = url->path_get(&len); + if (tmp) { + h->update(tmp, len); + } + + if (!parent_record->ignore_query) { + tmp = url->query_get(&len); + if (tmp) { + h->update("?", 1); + h->update(tmp, len); + } + } + + h->final(); + + return h->get(); +} + +void +ParentConsistentHash::lookupParent(bool first_call, ParentResult *result, RequestData *rdata) +{ + ATSHash64Sip24 hash; + ATSConsistentHash *fhash; + HttpRequestData *request_info = static_cast(rdata); + bool parentRetry = false; + bool wrap_around[2] = {false, false}; + uint64_t path_hash = 0; + uint32_t last_lookup; + pRecord *prtmp = NULL, *pRec = NULL; + + Debug("parent_select", "ParentConsistentHash::%s(): Using a consistent hash parent selection strategy.", __func__); + ink_assert(numParents(result) > 0 || parent_record->go_direct == true); + + // Should only get into this state if we are supposed to go direct. + if (parents[PRIMARY] == NULL && parents[SECONDARY] == NULL) { + if (parent_record->go_direct == true) { + result->r = PARENT_DIRECT; + } else { + result->r = PARENT_FAIL; + } + result->hostname = NULL; + result->port = 0; + return; + } + + // findParent() call if first_call. + if (first_call) { + last_lookup = PRIMARY; + path_hash = getPathHash(request_info, (ATSHash64 *)&hash); + fhash = chash[PRIMARY]; + if (path_hash) { + prtmp = (pRecord *)fhash->lookup_by_hashval(path_hash, &chashIter[last_lookup], &wrap_around[last_lookup]); + if (prtmp) + pRec = (parents[last_lookup] + prtmp->idx); + } + // else called by nextParent(). + } else { + if (chash[SECONDARY] != NULL) { + last_lookup = SECONDARY; + fhash = chash[SECONDARY]; + path_hash = getPathHash(request_info, (ATSHash64 *)&hash); + if (path_hash) { + prtmp = (pRecord *)fhash->lookup_by_hashval(path_hash, &chashIter[last_lookup], &wrap_around[last_lookup]); + if (prtmp) + pRec = (parents[last_lookup] + prtmp->idx); + } + } else { + last_lookup = PRIMARY; + fhash = chash[PRIMARY]; + do { // search until we've selected a different parent. + prtmp = (pRecord *)fhash->lookup(NULL, 0, &chashIter[last_lookup], &wrap_around[last_lookup], &hash); + if (prtmp) + pRec = (parents[last_lookup] + prtmp->idx); + } while (prtmp && strcmp(prtmp->hostname, result->hostname) == 0); + } + } + + // didn't find a parent or the parent is marked unavailable. + if (!pRec || (pRec && !pRec->available)) { + do { + if (pRec && !pRec->available) { + Debug("parent_select", "Parent.failedAt = %u, retry = %u, xact_start = %u", (unsigned int)pRec->failedAt, + (unsigned int)c_params->ParentRetryTime, (unsigned int)request_info->xact_start); + if ((pRec->failedAt + c_params->ParentRetryTime) < request_info->xact_start) { + parentRetry = true; + // make sure that the proper state is recorded in the result structure + // so that recordRetrySuccess() finds the proper record. + result->last_parent = prtmp->idx; + result->last_lookup = last_lookup; + result->retry = parentRetry; + if (!parent_record->parent_is_proxy) { + result->r = PARENT_ORIGIN; + } else { + result->r = PARENT_SPECIFIED; + } + recordRetrySuccess(result); + Debug("parent_select", "Down parent %s is now retryable, marked it available.", pRec->hostname); + break; + } + } + Debug("parent_select", "wrap_around[PRIMARY]: %d, wrap_around[SECONDARY]: %d", wrap_around[PRIMARY], wrap_around[SECONDARY]); + if (!wrap_around[PRIMARY] || (chash[SECONDARY] != NULL && !wrap_around[SECONDARY])) { + Debug("parent_select", "Selected parent %s is not available, looking up another parent.", pRec->hostname); + if (chash[SECONDARY] != NULL && !wrap_around[SECONDARY]) { + fhash = chash[SECONDARY]; + last_lookup = SECONDARY; + } else { + fhash = chash[PRIMARY]; + last_lookup = PRIMARY; + } + prtmp = (pRecord *)fhash->lookup(NULL, 0, &chashIter[last_lookup], &wrap_around[last_lookup], &hash); + if (prtmp) { + pRec = (parents[last_lookup] + prtmp->idx); + Debug("parent_select", "Selected a new parent: %s.", pRec->hostname); + } + } + if (wrap_around[PRIMARY] && chash[SECONDARY] == NULL) { + Debug("parent_select", "No available parents."); + break; + } + if (wrap_around[PRIMARY] && chash[SECONDARY] != NULL && wrap_around[SECONDARY]) { + Debug("parent_select", "No available parents."); + break; + } + } while (!prtmp || !pRec->available); + } + + // use the available parent. + if (pRec && pRec->available) { + if (!parent_record->parent_is_proxy) { + result->r = PARENT_ORIGIN; + } else { + result->r = PARENT_SPECIFIED; + } + result->hostname = pRec->hostname; + result->port = pRec->port; + result->last_parent = pRec->idx; + result->last_lookup = last_lookup; + result->retry = parentRetry; + ink_assert(result->hostname != NULL); + ink_assert(result->port != 0); + Debug("parent_select", "Chosen parent: %s.%d", result->hostname, result->port); + } else { + if (parent_record->go_direct == true && parent_record->parent_is_proxy) { + result->r = PARENT_DIRECT; + } else { + result->r = PARENT_FAIL; + } + result->hostname = NULL; + result->port = 0; + result->retry = false; + } + + return; +} + +void +ParentConsistentHash::markParentDown(ParentResult *result) +{ + time_t now; + pRecord *pRec; + int new_fail_count = 0; + + Debug("parent_select", "Starting ParentConsistentHash::markParentDown()"); + + // Make sure that we are being called back with with a + // result structure with a parent + ink_assert(result->r == PARENT_SPECIFIED || result->r == PARENT_ORIGIN); + if (result->r != PARENT_SPECIFIED && result->r != PARENT_ORIGIN) { + return; + } + // If we were set through the API we currently have not failover + // so just return fail + if (result->rec == extApiRecord) { + return; + } + + ink_assert((result->last_parent) < numParents(result)); + pRec = parents[result->last_lookup] + result->last_parent; + + // If the parent has already been marked down, just increment + // the failure count. If this is the first mark down on a + // parent we need to both set the failure time and set + // count to one. It's possible for the count and time get out + // sync due there being no locks. Therefore the code should + // handle this condition. If this was the result of a retry, we + // must update move the failedAt timestamp to now so that we continue + // negative cache the parent + if (pRec->failedAt == 0 || result->retry == true) { + // Reread the current time. We want this to be accurate since + // it relates to how long the parent has been down. + now = time(NULL); + + // Mark the parent as down + ink_atomic_swap(&pRec->failedAt, now); + + // If this is clean mark down and not a failed retry, we + // must set the count to reflect this + if (result->retry == false) { + new_fail_count = pRec->failCount = 1; + } + + Note("Parent %s marked as down %s:%d", (result->retry) ? "retry" : "initially", pRec->hostname, pRec->port); + + } else { + int old_count = ink_atomic_increment(&pRec->failCount, 1); + + Debug("parent_select", "Parent fail count increased to %d for %s:%d", old_count + 1, pRec->hostname, pRec->port); + new_fail_count = old_count + 1; + } + + if (new_fail_count > 0 && new_fail_count == c_params->FailThreshold) { + Note("Failure threshold met, http parent proxy %s:%d marked down", pRec->hostname, pRec->port); + pRec->available = false; + Debug("parent_select", "Parent %s:%d marked unavailable, pRec->available=%d", pRec->hostname, pRec->port, pRec->available); + } +} + +uint32_t +ParentConsistentHash::numParents(ParentResult *result) +{ + uint32_t n = 0; + + switch (result->last_lookup) { + case PRIMARY: + n = parent_record->num_parents; + break; + case SECONDARY: + n = parent_record->num_secondary_parents; + break; + } + + return n; +} + +void +ParentConsistentHash::recordRetrySuccess(ParentResult *result) +{ + pRecord *pRec; + + // Make sure that we are being called back with with a + // result structure with a parent that is being retried + ink_release_assert(result->retry == true); + ink_assert(result->r == PARENT_SPECIFIED || result->r == PARENT_ORIGIN); + if (result->r != PARENT_SPECIFIED && result->r != PARENT_ORIGIN) { + return; + } + // If we were set through the API we currently have not failover + // so just return fail + if (result->rec == extApiRecord) { + ink_assert(0); + return; + } + + ink_assert((result->last_parent) < numParents(result)); + pRec = parents[result->last_lookup] + result->last_parent; + pRec->available = true; + Debug("parent_select", "%s:%s(): marked %s:%d available.", __FILE__, __func__, pRec->hostname, pRec->port); + + ink_atomic_swap(&pRec->failedAt, (time_t)0); + int old_count = ink_atomic_swap(&pRec->failCount, 0); + + if (old_count > 0) { + Note("http parent proxy %s:%d restored", pRec->hostname, pRec->port); + } +} diff --git a/proxy/ParentConsistentHash.h b/proxy/ParentConsistentHash.h new file mode 100644 index 00000000000..cefc80734c2 --- /dev/null +++ b/proxy/ParentConsistentHash.h @@ -0,0 +1,62 @@ +/** @file + + A brief file description + + @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. + */ + +/***************************************************************************** + * + * ParentConsistentHash.h - Interface to Parent Consistent Hash. + * + ****************************************************************************/ + +#ifndef _PARENT_CONSISTENT_HASH_H +#define _PARENT_CONSISTENT_HASH_H + +#include "ts/HashSip.h" +#include "ParentSelection.h" + +// +// Implementation of round robin based upon consistent hash of the URL, +// ParentRR_t = P_CONSISTENT_HASH. +// +class ParentConsistentHash : ParentSelectionBase, public ParentSelectionStrategy +{ + // there are two hashes PRIMARY parents + // and SECONDARY parents. + ATSHash64Sip24 hash[2]; + ATSConsistentHash *chash[2]; + ATSConsistentHashIter chashIter[2]; + pRecord *parents[2]; + bool foundParents[2][MAX_PARENTS]; + +public: + static const int PRIMARY = 0; + static const int SECONDARY = 1; + ParentConsistentHash(ParentRecord *_parent_record); + ~ParentConsistentHash(); + uint64_t getPathHash(HttpRequestData *hrdata, ATSHash64 *h); + void lookupParent(bool firstCall, ParentResult *result, RequestData *rdata); + void markParentDown(ParentResult *result); + uint32_t numParents(ParentResult *result); + void recordRetrySuccess(ParentResult *result); +}; + +#endif diff --git a/proxy/ParentRoundRobin.cc b/proxy/ParentRoundRobin.cc new file mode 100644 index 00000000000..6920209d181 --- /dev/null +++ b/proxy/ParentRoundRobin.cc @@ -0,0 +1,284 @@ +/** @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 "ParentRoundRobin.h" + +ParentRoundRobin::ParentRoundRobin(ParentRecord *_parent_record) +{ + parent_record = _parent_record; + round_robin_type = parent_record->round_robin; + + if (is_debug_tag_set("parent_select")) { + switch (round_robin_type) { + case P_NO_ROUND_ROBIN: + Debug("parent_select", "Using a round robin parent selection strategy of type P_NO_ROUND_ROBIN."); + break; + case P_STRICT_ROUND_ROBIN: + Debug("parent_select", "Using a round robin parent selection strategy of type P_STRICT_ROUND_ROBIN."); + break; + case P_HASH_ROUND_ROBIN: + Debug("parent_select", "Using a round robin parent selection strategy of type P_HASH_ROUND_ROBIN."); + break; + default: + // should never see this, there is a problem if you do. + Debug("parent_select", "Using a round robin parent selection strategy of type UNKNOWN TYPE."); + break; + } + } +} + +ParentRoundRobin::~ParentRoundRobin() +{ + if (parent_record) { + delete parent_record; + } +} + +void +ParentRoundRobin::lookupParent(bool first_call, ParentResult *result, RequestData *rdata) +{ + Debug("parent_select", "In ParentRoundRobin::lookupParent(): Using a round robin parent selection strategy."); + int cur_index = 0; + bool parentUp = false; + bool parentRetry = false; + bool bypass_ok = (parent_record->go_direct == true && c_params->DNS_ParentOnly == 0); + + HttpRequestData *request_info = static_cast(rdata); + + ink_assert(numParents(result) > 0 || parent_record->go_direct == true); + + if (first_call) { + if (parent_record->parents == NULL) { + // We should only get into this state if + // if we are supposed to go direct + ink_assert(parent_record->go_direct == true); + // Could not find a parent + if (parent_record->go_direct == true && parent_record->parent_is_proxy) { + result->r = PARENT_DIRECT; + } else { + result->r = PARENT_FAIL; + } + + result->hostname = NULL; + result->port = 0; + return; + } else { + switch (parent_record->round_robin) { + case P_HASH_ROUND_ROBIN: + // INKqa12817 - make sure to convert to host byte order + // Why was it important to do host order here? And does this have any + // impact with the transition to IPv6? The IPv4 functionality is + // preserved for now anyway as ats_ip_hash returns the 32-bit address in + // that case. + if (rdata->get_client_ip() != NULL) { + cur_index = ntohl(ats_ip_hash(rdata->get_client_ip())) % parent_record->num_parents; + } else { + cur_index = 0; + } + break; + case P_STRICT_ROUND_ROBIN: + cur_index = ink_atomic_increment((int32_t *)&parent_record->rr_next, 1); + cur_index = cur_index % parent_record->num_parents; + break; + case P_NO_ROUND_ROBIN: + cur_index = result->start_parent = 0; + break; + default: + ink_release_assert(0); + } + } + } else { + // Move to next parent due to failure + cur_index = (result->last_parent + 1) % parent_record->num_parents; + + // Check to see if we have wrapped around + if ((unsigned int)cur_index == result->start_parent) { + // We've wrapped around so bypass if we can + if (bypass_ok == true) { + // Could not find a parent + if (parent_record->go_direct == true && parent_record->parent_is_proxy) { + result->r = PARENT_DIRECT; + } else { + result->r = PARENT_FAIL; + } + result->hostname = NULL; + result->port = 0; + return; + } else { + // Bypass disabled so keep trying, ignoring whether we think + // a parent is down or not + result->wrap_around = true; + } + } + } + // Loop through the array of parent seeing if any are up or + // should be retried + do { + // DNS ParentOnly inhibits bypassing the parent so always return that t + if ((parent_record->parents[cur_index].failedAt == 0) || + (parent_record->parents[cur_index].failCount < c_params->FailThreshold)) { + Debug("parent_select", "FailThreshold = %d", c_params->FailThreshold); + Debug("parent_select", "Selecting a parent due to little failCount" + "(faileAt: %u failCount: %d)", + (unsigned)parent_record->parents[cur_index].failedAt, parent_record->parents[cur_index].failCount); + parentUp = true; + } else { + if ((result->wrap_around) || + ((parent_record->parents[cur_index].failedAt + c_params->ParentRetryTime) < request_info->xact_start)) { + Debug("parent_select", "Parent[%d].failedAt = %u, retry = %u,xact_start = %" PRId64 " but wrap = %d", cur_index, + (unsigned)parent_record->parents[cur_index].failedAt, c_params->ParentRetryTime, (int64_t)request_info->xact_start, + result->wrap_around); + // Reuse the parent + parentUp = true; + parentRetry = true; + Debug("parent_select", "Parent marked for retry %s:%d", parent_record->parents[cur_index].hostname, + parent_record->parents[cur_index].port); + } else { + parentUp = false; + } + } + + if (parentUp == true) { + if (!parent_record->parent_is_proxy) { + result->r = PARENT_ORIGIN; + } else { + result->r = PARENT_SPECIFIED; + } + result->hostname = parent_record->parents[cur_index].hostname; + result->port = parent_record->parents[cur_index].port; + result->last_parent = cur_index; + result->retry = parentRetry; + ink_assert(result->hostname != NULL); + ink_assert(result->port != 0); + Debug("parent_select", "Chosen parent = %s.%d", result->hostname, result->port); + return; + } + cur_index = (cur_index + 1) % parent_record->num_parents; + } while ((unsigned int)cur_index != result->start_parent); + + if (parent_record->go_direct == true && parent_record->parent_is_proxy) { + result->r = PARENT_DIRECT; + } else { + result->r = PARENT_FAIL; + } + + result->hostname = NULL; + result->port = 0; +} + +uint32_t +ParentRoundRobin::numParents(ParentResult *result) +{ + return parent_record->num_parents; +} + +void +ParentRoundRobin::markParentDown(ParentResult *result) +{ + time_t now; + pRecord *pRec; + int new_fail_count = 0; + + Debug("parent_select", "Starting ParentRoundRobin::markParentDown()"); + // Make sure that we are being called back with with a + // result structure with a parent + ink_assert(result->r == PARENT_SPECIFIED || result->r == PARENT_ORIGIN); + if (result->r != PARENT_SPECIFIED && result->r != PARENT_ORIGIN) { + return; + } + // If we were set through the API we currently have not failover + // so just return fail + if (result->rec == extApiRecord) { + return; + } + + ink_assert((int)(result->last_parent) < result->rec->num_parents); + pRec = result->rec->parents + result->last_parent; + + // If the parent has already been marked down, just increment + // the failure count. If this is the first mark down on a + // parent we need to both set the failure time and set + // count to one. It's possible for the count and time get out + // sync due there being no locks. Therefore the code should + // handle this condition. If this was the result of a retry, we + // must update move the failedAt timestamp to now so that we continue + // negative cache the parent + if (pRec->failedAt == 0 || result->retry == true) { + // Reread the current time. We want this to be accurate since + // it relates to how long the parent has been down. + now = time(NULL); + + // Mark the parent as down + ink_atomic_swap(&pRec->failedAt, now); + + // If this is clean mark down and not a failed retry, we + // must set the count to reflect this + if (result->retry == false) { + new_fail_count = pRec->failCount = 1; + } + + Note("Parent %s marked as down %s:%d", (result->retry) ? "retry" : "initially", pRec->hostname, pRec->port); + + } else { + int old_count = ink_atomic_increment(&pRec->failCount, 1); + + Debug("parent_select", "Parent fail count increased to %d for %s:%d", old_count + 1, pRec->hostname, pRec->port); + new_fail_count = old_count + 1; + } + + if (new_fail_count > 0 && new_fail_count == c_params->FailThreshold) { + Note("Failure threshold met, http parent proxy %s:%d marked down", pRec->hostname, pRec->port); + pRec->available = false; + Debug("parent_select", "Parent marked unavailable, pRec->available=%d", pRec->available); + } +} + +void +ParentRoundRobin::recordRetrySuccess(ParentResult *result) +{ + pRecord *pRec; + + // Make sure that we are being called back with with a + // result structure with a parent that is being retried + ink_release_assert(result->retry == true); + ink_assert(result->r == PARENT_SPECIFIED || result->r == PARENT_ORIGIN); + if (result->r != PARENT_SPECIFIED && result->r != PARENT_ORIGIN) { + return; + } + // If we were set through the API we currently have not failover + // so just return fail + if (result->rec == extApiRecord) { + ink_assert(0); + return; + } + + ink_assert((int)(result->last_parent) < result->rec->num_parents); + pRec = result->rec->parents + result->last_parent; + pRec->available = true; + + ink_atomic_swap(&pRec->failedAt, (time_t)0); + int old_count = ink_atomic_swap(&pRec->failCount, 0); + + if (old_count > 0) { + Note("http parent proxy %s:%d restored", pRec->hostname, pRec->port); + } +} diff --git a/proxy/ParentRoundRobin.h b/proxy/ParentRoundRobin.h new file mode 100644 index 00000000000..c8a77b489c1 --- /dev/null +++ b/proxy/ParentRoundRobin.h @@ -0,0 +1,48 @@ +/** @file + + A brief file description + + @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. + */ + +/***************************************************************************** + * + * ParentRoundRobin.h - Implementation of various round robin strategies. + * + *****************************************************************************/ + +#ifndef _PARENT_ROUND_ROBIN_H +#define _PARENT_ROUND_ROBIN_H + +#include "ParentSelection.h" + +class ParentRoundRobin : ParentSelectionBase, public ParentSelectionStrategy +{ + ParentRR_t round_robin_type; + +public: + ParentRoundRobin(ParentRecord *_parent_record); + ~ParentRoundRobin(); + void lookupParent(bool firstCall, ParentResult *result, RequestData *rdata); + void markParentDown(ParentResult *result); + uint32_t numParents(ParentResult *result); + void recordRetrySuccess(ParentResult *result); +}; + +#endif diff --git a/proxy/ParentSelection.cc b/proxy/ParentSelection.cc index 9000982329b..7776519caa9 100644 --- a/proxy/ParentSelection.cc +++ b/proxy/ParentSelection.cc @@ -20,11 +20,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include "ts/ink_platform.h" -#include "ts/HashSip.h" -#include "ts/Tokenizer.h" #include "P_EventSystem.h" #include "ParentSelection.h" +#include "ParentConsistentHash.h" +#include "ParentRoundRobin.h" #include "ControlMatcher.h" #include "Main.h" #include "Error.h" @@ -50,8 +49,8 @@ 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 *dns_parent_only_var = "proxy.config.http.no_dns_just_forward_to_parent"; -static const char *ParentResultStr[] = {"Parent_Undefined", "Parent_Direct", "Parent_Specified", "Parent_Failed"}; - +static const char *ParentResultStr[] = {"PARENT_UNDEFINED", "PARENT_DIRECT", "PARENT_SPECIFIED", + "PARENT_AGENT", "PARENT_FAIL", "PARENT_ORIGIN"}; static const char *ParentRRStr[] = {"false", "strict", "true", "consistent"}; // @@ -66,122 +65,65 @@ enum ParentCB_t { PARENT_DNS_ONLY_CB, }; -// If the parent was set by the external customer api, -// our HttpRequestData structure told us what parent to -// use and we are only called to preserve clean interface -// between HttpTransact & the parent selection code. The following -ParentRecord *const extApiRecord = (ParentRecord *)0xeeeeffff; - -ParentConfigParams::ParentConfigParams() - : ParentTable(NULL), DefaultParent(NULL), ParentRetryTime(30), ParentEnable(0), FailThreshold(10), DNS_ParentOnly(0) +ParentSelectionBase::ParentSelectionBase() { -} + bool enable = false; + int32_t retry_time = 0; + int32_t fail_threshold = 0; + int32_t dns_parent_only = 0; -ParentConfigParams::~ParentConfigParams() -{ - if (ParentTable) { - delete ParentTable; - } + this->c_params = (struct config_params *)ats_malloc(sizeof(struct config_params)); - if (DefaultParent) { - delete DefaultParent; - } -} - -int ParentConfig::m_id = 0; - -// -// Begin API functions -// -void -ParentConfig::startup() -{ - parentConfigUpdate = new ConfigUpdateHandler(); + // Handle parent timeout + PARENT_ReadConfigInteger(retry_time, retry_var); + c_params->ParentRetryTime = retry_time; - // Load the initial configuration - reconfigure(); + // Handle parent enable + PARENT_ReadConfigInteger(enable, enable_var); + c_params->ParentEnable = enable; - // 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); + // Handle the fail threshold + PARENT_ReadConfigInteger(fail_threshold, threshold_var); + c_params->FailThreshold = fail_threshold; - // Fail Threshold - parentConfigUpdate->attach(threshold_var); + // Handle dns parent only + PARENT_ReadConfigInteger(dns_parent_only, dns_parent_only_var); + c_params->DNS_ParentOnly = dns_parent_only; - // DNS Parent Only - parentConfigUpdate->attach(dns_parent_only_var); + parent_record = NULL; } -void -ParentConfig::reconfigure() +ParentConfigParams::ParentConfigParams(P_table *_parent_table) { char *default_val = NULL; - int retry_time = 30; - int enable = 0; - int fail_threshold; - int dns_parent_only; + parent_table = _parent_table; + bool enable = false; + int32_t retry_time = 0; + int32_t fail_threshold = 0; + int32_t dns_parent_only = 0; - ParentConfigParams *params; - params = new ParentConfigParams; - - // Allocate parent table - params->ParentTable = new P_table(file_var, modulePrefix, &http_dest_tags); - - // Handle default parent - PARENT_ReadConfigStringAlloc(default_val, default_var); - params->DefaultParent = createDefaultParent(default_val); - ats_free(default_val); + this->c_params = (struct config_params *)ats_malloc(sizeof(struct config_params)); // Handle parent timeout PARENT_ReadConfigInteger(retry_time, retry_var); - params->ParentRetryTime = retry_time; + c_params->ParentRetryTime = retry_time; // Handle parent enable PARENT_ReadConfigInteger(enable, enable_var); - params->ParentEnable = enable; + c_params->ParentEnable = enable; // Handle the fail threshold PARENT_ReadConfigInteger(fail_threshold, threshold_var); - params->FailThreshold = fail_threshold; + c_params->FailThreshold = fail_threshold; // Handle dns parent only PARENT_ReadConfigInteger(dns_parent_only, dns_parent_only_var); - params->DNS_ParentOnly = dns_parent_only; - - 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(); + c_params->DNS_ParentOnly = dns_parent_only; - printf("Parent Selection Config\n"); - printf("\tEnabled %d\tRetryTime %d\tParent DNS Only %d\n", params->ParentEnable, params->ParentRetryTime, params->DNS_ParentOnly); - if (params->DefaultParent == NULL) { - printf("\tNo Default Parent\n"); - } else { - printf("\tDefault Parent:\n"); - params->DefaultParent->Print(); - } - printf(" "); - params->ParentTable->Print(); - - ParentConfig::release(params); + // Handle default parent + PARENT_ReadConfigStringAlloc(default_val, default_var); + DefaultParent = createDefaultParent(default_val); + ats_free(default_val); } bool @@ -190,31 +132,18 @@ ParentConfigParams::apiParentExists(HttpRequestData *rdata) return (rdata->api_info && rdata->api_info->parent_proxy_name != NULL && rdata->api_info->parent_proxy_port > 0); } -bool -ParentConfigParams::parentExists(HttpRequestData *rdata) -{ - ParentResult junk; - - findParent(rdata, &junk); - - if (junk.r == PARENT_SPECIFIED) { - return true; - } else { - return false; - } -} - void ParentConfigParams::findParent(HttpRequestData *rdata, ParentResult *result) { - P_table *tablePtr = ParentTable; + P_table *tablePtr = parent_table; ParentRecord *defaultPtr = DefaultParent; ParentRecord *rec; + Debug("parent_select", "In ParentSelectionBase::findParent(): parent_table: %p.", parent_table); ink_assert(result->r == PARENT_UNDEFINED); // Check to see if we are enabled - if (ParentEnable == 0) { + if (c_params->ParentEnable == 0) { result->r = PARENT_DIRECT; return; } @@ -223,9 +152,6 @@ ParentConfigParams::findParent(HttpRequestData *rdata, ParentResult *result) result->epoch = tablePtr; result->line_number = 0xffffffff; result->wrap_around = false; - // if this variabel is not set, we have problems: the code in - // FindParent relies on the value of start_parent and when it is not - // initialized, the code in FindParent can get into an infinite loop! result->start_parent = 0; result->last_parent = 0; @@ -254,160 +180,57 @@ ParentConfigParams::findParent(HttpRequestData *rdata, ParentResult *result) rec = result->rec = defaultPtr; } else { result->r = PARENT_DIRECT; - Debug("cdn", "Returning PARENT_DIRECT (no parents were found)"); + Debug("parent_select", "Returning PARENT_DIRECT (no parents were found)"); return; } } - // Loop through the set of parents to see if any are - // available - Debug("cdn", "Calling FindParent from findParent"); - - // Bug INKqa08251: - // If a parent proxy is set by the API, - // no need to call FindParent() - if (rec != extApiRecord) - rec->FindParent(true, result, rdata, this); - - if (is_debug_tag_set("parent_select") || is_debug_tag_set("cdn")) { - switch (result->r) { - case PARENT_UNDEFINED: - Debug("cdn", "PARENT_UNDEFINED"); - break; - case PARENT_FAIL: - Debug("cdn", "PARENT_FAIL"); - break; - case PARENT_DIRECT: - Debug("cdn", "PARENT_DIRECT"); - break; - case PARENT_SPECIFIED: - Debug("cdn", "PARENT_SPECIFIED"); - break; - default: - // Handled here: - // PARENT_AGENT - break; - } - const char *host = rdata->get_host(); - - switch (result->r) { - case PARENT_UNDEFINED: - case PARENT_FAIL: - case PARENT_DIRECT: - Debug("parent_select", "Result for %s was %s", host, ParentResultStr[result->r]); - break; - case PARENT_SPECIFIED: - Debug("parent_select", "Result for %s was parent %s:%d", host, result->hostname, result->port); - break; - default: - // Handled here: - // PARENT_AGENT - break; - } + if (rec != extApiRecord) { + // first lookup + rec->lookup_strategy->lookupParent(true, result, rdata); } -} - -void -ParentConfigParams::recordRetrySuccess(ParentResult *result) -{ - pRecord *pRec; + const char *host = rdata->get_host(); - // Make sure that we are being called back with with a - // result structure with a parent that is being retried - ink_release_assert(result->retry == true); - ink_assert(result->r == PARENT_SPECIFIED); - if (result->r != PARENT_SPECIFIED) { - return; - } - // If we were set through the API we currently have not failover - // so just return fail - if (result->rec == extApiRecord) { - ink_assert(0); - return; - } - - ink_assert((int)(result->last_parent) < result->rec->num_parents); - pRec = result->rec->parents + result->last_parent; - - pRec->available = true; - - ink_atomic_swap(&pRec->failedAt, (time_t)0); - int old_count = ink_atomic_swap(&pRec->failCount, 0); - - if (old_count > 0) { - Note("http parent proxy %s:%d restored", pRec->hostname, pRec->port); - } -} - -void -ParentConfigParams::markParentDown(ParentResult *result) -{ - time_t now; - pRecord *pRec; - int new_fail_count = 0; - - // Make sure that we are being called back with with a - // result structure with a parent - ink_assert(result->r == PARENT_SPECIFIED); - if (result->r != PARENT_SPECIFIED) { - return; - } - // If we were set through the API we currently have not failover - // so just return fail - if (result->rec == extApiRecord) { - return; - } - - ink_assert((int)(result->last_parent) < result->rec->num_parents); - pRec = result->rec->parents + result->last_parent; - - // If the parent has already been marked down, just increment - // the failure count. If this is the first mark down on a - // parent we need to both set the failure time and set - // count to one. It's possible for the count and time get out - // sync due there being no locks. Therefore the code should - // handle this condition. If this was the result of a retry, we - // must update move the failedAt timestamp to now so that we continue - // negative cache the parent - if (pRec->failedAt == 0 || result->retry == true) { - // Reread the current time. We want this to be accurate since - // it relates to how long the parent has been down. - now = time(NULL); - - // Mark the parent as down - ink_atomic_swap(&pRec->failedAt, now); - - // If this is clean mark down and not a failed retry, we - // must set the count to reflect this - if (result->retry == false) { - new_fail_count = pRec->failCount = 1; - } - - Debug("parent_select", "Parent %s marked as down %s:%d", (result->retry) ? "retry" : "initially", pRec->hostname, pRec->port); - - } else { - int old_count = ink_atomic_increment(&pRec->failCount, 1); - - Debug("parent_select", "Parent fail count increased to %d for %s:%d", old_count + 1, pRec->hostname, pRec->port); - new_fail_count = old_count + 1; - } - - if (new_fail_count > 0 && new_fail_count == FailThreshold) { - Note("http parent proxy %s:%d marked down", pRec->hostname, pRec->port); - pRec->available = false; + switch (result->r) { + case PARENT_UNDEFINED: + Debug("parent_select", "PARENT_UNDEFINED"); + Debug("parent_select", "Result for %s was %s", host, ParentResultStr[result->r]); + 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->r]); + break; + case PARENT_ORIGIN: + Debug("parent_select", "PARENT_ORIGIN"); + Debug("parent_select", "Result for %s was parent %s:%d", host, result->hostname, result->port); + 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 = ParentTable; + P_table *tablePtr = parent_table; + + Debug("parent_select", "ParentSelectionBase::nextParent(): parent_table: %p, result->rec: %p, result->epoch: %p", parent_table, + result->rec, result->epoch); // Make sure that we are being called back with a // result structure with a parent - ink_assert(result->r == PARENT_SPECIFIED); - if (result->r != PARENT_SPECIFIED) { + ink_assert(result->r == PARENT_SPECIFIED || result->r == PARENT_ORIGIN); + if (result->r != PARENT_SPECIFIED && result->r != PARENT_ORIGIN) { result->r = PARENT_FAIL; return; } @@ -418,268 +241,154 @@ ParentConfigParams::nextParent(HttpRequestData *rdata, ParentResult *result) result->r = PARENT_FAIL; return; } - // The epoch pointer is a legacy from the time when the tables - // would be swapped and deleted in the future. I'm using the - // pointer now to ensure that the ParentConfigParams structure - // is properly used. The table should never change out from - // under the a http transaction + Debug("parent_select", "ParentSelectionBase::nextParent(): result->r: %d, tablePtr: %p, result->epoch: %p", result->r, tablePtr, + result->epoch); ink_release_assert(tablePtr == result->epoch); // Find the next parent in the array - Debug("cdn", "Calling FindParent from nextParent"); - result->rec->FindParent(false, result, rdata, this); + Debug("parent_select", "Calling lookupParent() from nextParent"); + result->rec->lookup_strategy->lookupParent(false, result, rdata); + + const char *host = rdata->get_host(); switch (result->r) { case PARENT_UNDEFINED: - Debug("cdn", "PARENT_UNDEFINED"); + Debug("parent_select", "PARENT_UNDEFINED"); + Debug("parent_select", "Retry result for %s was %s", host, ParentResultStr[result->r]); break; case PARENT_FAIL: - Debug("cdn", "PARENT_FAIL"); + Debug("parent_select", "PARENT_FAIL"); + Debug("parent_select", "Retry result for %s was %s", host, ParentResultStr[result->r]); break; case PARENT_DIRECT: - Debug("cdn", "PARENT_DIRECT"); + Debug("parent_select", "PARENT_DIRECT"); + Debug("parent_select", "Retry result for %s was %s", host, ParentResultStr[result->r]); + break; + case PARENT_ORIGIN: + Debug("parent_select", "PARENT_ORIGIN"); + Debug("parent_select", "Retry result for %s was parent %s:%d", host, result->hostname, result->port); break; case PARENT_SPECIFIED: - Debug("cdn", "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; } +} - if (is_debug_tag_set("parent_select")) { - const char *host = rdata->get_host(); - - switch (result->r) { - case PARENT_UNDEFINED: - case PARENT_FAIL: - case PARENT_DIRECT: - Debug("parent_select", "Retry result for %s was %s", host, ParentResultStr[result->r]); - 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.r == PARENT_SPECIFIED || result.r == PARENT_ORIGIN) { + return true; + } else { + return false; } } -// -// End API functions -// +int ParentConfig::m_id = 0; void -ParentRecord::FindParent(bool first_call, ParentResult *result, RequestData *rdata, ParentConfigParams *config) +ParentConfig::startup() { - Debug("cdn", "Entering FindParent (the inner loop)"); - int cur_index = 0; - bool parentUp = false; - bool parentRetry = false; - bool bypass_ok = (go_direct == true && config->DNS_ParentOnly == 0); - char *url, *path = NULL; - ATSHash64Sip24 hash; - pRecord *prtmp = NULL; - - HttpRequestData *request_info = (HttpRequestData *)rdata; - - ink_assert(num_parents > 0 || go_direct == true); - - if (first_call == true) { - if (parents == NULL) { - // We should only get into this state if - // if we are supposed to go dirrect - ink_assert(go_direct == true); - goto NO_PARENTS; - } else if (round_robin == true) { - cur_index = ink_atomic_increment((int32_t *)&rr_next, 1); - cur_index = result->start_parent = cur_index % num_parents; - } else { - switch (round_robin) { - case P_STRICT_ROUND_ROBIN: - cur_index = ink_atomic_increment((int32_t *)&rr_next, 1); - cur_index = cur_index % num_parents; - break; - case P_HASH_ROUND_ROBIN: - // INKqa12817 - make sure to convert to host byte order - // Why was it important to do host order here? And does this have any - // impact with the transition to IPv6? The IPv4 functionality is - // preserved for now anyway as ats_ip_hash returns the 32-bit address in - // that case. - if (rdata->get_client_ip() != NULL) { - cur_index = ntohl(ats_ip_hash(rdata->get_client_ip())) % num_parents; - } else { - cur_index = 0; - } - break; - case P_CONSISTENT_HASH: - url = rdata->get_string(); - path = strstr(url + 7, "/"); - if (path) { - prtmp = (pRecord *)chash->lookup(path, &(result->chashIter), NULL, (ATSHash64 *)&hash); - if (prtmp) { - cur_index = prtmp->idx; - result->foundParents[cur_index] = true; - result->start_parent++; - } else { - Error("Consistent Hash loopup returned NULL"); - cur_index = ink_atomic_increment((int32_t *)&rr_next, 1); - cur_index = cur_index % num_parents; - } - } else { - Error("Could not find path in URL: %s", url); - cur_index = ink_atomic_increment((int32_t *)&rr_next, 1); - cur_index = cur_index % num_parents; - } - break; - case P_NO_ROUND_ROBIN: - cur_index = result->start_parent = 0; - break; - default: - ink_release_assert(0); - } - } - } else { - if (round_robin == P_CONSISTENT_HASH) { - if (result->start_parent == (unsigned int)num_parents) { - result->wrap_around = true; - result->start_parent = 0; - memset(result->foundParents, 0, sizeof(result->foundParents)); - url = rdata->get_string(); - path = strstr(url + 7, "/"); - } - - do { - prtmp = (pRecord *)chash->lookup(path, &(result->chashIter), NULL, (ATSHash64 *)&hash); - path = NULL; - } while (result->foundParents[prtmp->idx]); + parentConfigUpdate = new ConfigUpdateHandler(); - cur_index = prtmp->idx; - result->foundParents[cur_index] = true; - result->start_parent++; - } else { - // Move to next parent due to failure - cur_index = (result->last_parent + 1) % num_parents; - - // Check to see if we have wrapped around - if ((unsigned int)cur_index == result->start_parent) { - // We've wrapped around so bypass if we can - if (bypass_ok == true) { - goto NO_PARENTS; - } else { - // Bypass disabled so keep trying, ignoring whether we think - // a parent is down or not - FORCE_WRAP_AROUND: - result->wrap_around = true; - } - } - } - } + // Load the initial configuration + reconfigure(); - // Loop through the array of parent seeing if any are up or - // should be retried - do { - // DNS ParentOnly inhibits bypassing the parent so always return that t - if ((parents[cur_index].failedAt == 0) || (parents[cur_index].failCount < config->FailThreshold)) { - Debug("parent_select", "config->FailThreshold = %d", config->FailThreshold); - Debug("parent_select", "Selecting a down parent due to little failCount" - "(faileAt: %u failCount: %d)", - (unsigned)parents[cur_index].failedAt, parents[cur_index].failCount); - parentUp = true; - } else { - if ((result->wrap_around) || ((parents[cur_index].failedAt + config->ParentRetryTime) < request_info->xact_start)) { - Debug("parent_select", "Parent[%d].failedAt = %u, retry = %u,xact_start = %" PRId64 " but wrap = %d", cur_index, - (unsigned)parents[cur_index].failedAt, config->ParentRetryTime, (int64_t)request_info->xact_start, - result->wrap_around); - // Reuse the parent - parentUp = true; - parentRetry = true; - Debug("parent_select", "Parent marked for retry %s:%d", parents[cur_index].hostname, parents[cur_index].port); + // 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); - } else { - parentUp = false; - } - } + // Fail Threshold + parentConfigUpdate->attach(threshold_var); - if (parentUp == true) { - result->r = PARENT_SPECIFIED; - result->hostname = parents[cur_index].hostname; - result->port = parents[cur_index].port; - result->last_parent = cur_index; - result->retry = parentRetry; - ink_assert(result->hostname != NULL); - ink_assert(result->port != 0); - Debug("parent_select", "Chosen parent = %s.%d", result->hostname, result->port); - return; - } + // DNS Parent Only + parentConfigUpdate->attach(dns_parent_only_var); +} - if (round_robin == P_CONSISTENT_HASH) { - if (result->start_parent == (unsigned int)num_parents) { - result->wrap_around = false; - result->start_parent = 0; - memset(result->foundParents, 0, sizeof(result->foundParents)); - url = rdata->get_string(); - path = strstr(url + 7, "/"); - } +void +ParentConfig::reconfigure() +{ + ParentConfigParams *params = NULL; - do { - prtmp = (pRecord *)chash->lookup(path, &(result->chashIter), NULL, (ATSHash64 *)&hash); - path = NULL; - } while (result->foundParents[prtmp->idx]); + // Allocate parent table + P_table *pTable = new P_table(file_var, modulePrefix, &http_dest_tags); - cur_index = prtmp->idx; - result->foundParents[cur_index] = true; - result->start_parent++; - } else { - cur_index = (cur_index + 1) % num_parents; - } + params = new ParentConfigParams(pTable); + ink_assert(params != NULL); - } while ((round_robin == P_CONSISTENT_HASH ? result->wrap_around : ((unsigned int)cur_index != result->start_parent))); + m_id = configProcessor.set(m_id, params); - // We can't bypass so retry, taking any parent that we can - if (bypass_ok == false) { - goto FORCE_WRAP_AROUND; + if (is_debug_tag_set("parent_config")) { + ParentConfig::print(); } +} -NO_PARENTS: +// void ParentConfig::print +// +// Debugging function +// +void +ParentConfig::print() +{ + ParentConfigParams *params = ParentConfig::acquire(); - // Could not find a parent - if (this->go_direct == true) { - result->r = PARENT_DIRECT; + printf("Parent Selection Config\n"); + printf("\tEnabled %d\tRetryTime %d\tParent DNS Only %d\n", params->c_params->ParentEnable, params->c_params->ParentRetryTime, + params->c_params->DNS_ParentOnly); + if (params->DefaultParent == NULL) { + printf("\tNo Default Parent\n"); } else { - result->r = PARENT_FAIL; + printf("\tDefault Parent:\n"); + params->DefaultParent->Print(); } + printf(" "); + params->parent_table->Print(); - result->hostname = NULL; - result->port = 0; + ParentConfig::release(params); } -// const char* ParentRecord::ProcessParents(char* 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 +// 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) +ParentRecord::ProcessParents(char *val, bool isPrimary) { Tokenizer pTok(",; \t\r"); - int numTok; - const char *current; - int port; - char *tmp, *tmp2; - const char *errPtr; + 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) { + 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); @@ -687,7 +396,11 @@ ParentRecord::ProcessParents(char *val) return "No parents specified"; } // Allocate the parents array - this->parents = (pRecord *)ats_malloc(sizeof(pRecord) * numTok); + 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 // @@ -744,18 +457,35 @@ ParentRecord::ProcessParents(char *val) goto MERROR; } // Update the pRecords - 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].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 (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].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].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; } - num_parents = numTok; return NULL; MERROR: @@ -784,8 +514,10 @@ ParentRecord::DefaultInit(char *val) this->go_direct = true; this->round_robin = P_NO_ROUND_ROBIN; + this->ignore_query = false; this->scheme = NULL; - errPtr = ProcessParents(val); + this->parent_is_proxy = true; + errPtr = ProcessParents(val, true); if (errPtr != NULL) { errBuf = (char *)ats_malloc(1024); @@ -798,23 +530,6 @@ ParentRecord::DefaultInit(char *val) } } -void -ParentRecord::buildConsistentHash(void) -{ - ATSHash64Sip24 hash; - int i; - - if (chash) { - return; - } - - chash = new ATSConsistentHash(); - - for (i = 0; i < num_parents; i++) { - chash->insert(&(this->parents[i]), this->parents[i].weight, (ATSHash64 *)&hash); - } -} - // config_parse_error ParentRecord::Init(matcher_line* line_info) // // matcher_line* line_info - contains parsed label/value @@ -854,20 +569,17 @@ ParentRecord::Init(matcher_line *line_info) round_robin = P_NO_ROUND_ROBIN; } else if (strcasecmp(val, "consistent_hash") == 0) { round_robin = P_CONSISTENT_HASH; - if (this->parents != NULL) { - buildConsistentHash(); - } } else { round_robin = P_NO_ROUND_ROBIN; errPtr = "invalid argument to round_robin directive"; } used = true; - } else if (strcasecmp(label, "parent") == 0) { - errPtr = ProcessParents(val); + } 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; - if (round_robin == P_CONSISTENT_HASH) { - buildConsistentHash(); - } } else if (strcasecmp(label, "go_direct") == 0) { if (strcasecmp(val, "false") == 0) { go_direct = false; @@ -877,6 +589,21 @@ ParentRecord::Init(matcher_line *line_info) 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 { + parent_is_proxy = true; + } + used = true; } // Report errors generated by ProcessParents(); if (errPtr != NULL) { @@ -911,6 +638,26 @@ ParentRecord::Init(matcher_line *line_info) } } + 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."); + lookup_strategy = new ParentRoundRobin(this); + break; + case P_CONSISTENT_HASH: + TSDebug("parent_select", "allocating ParentConsistentHash() lookup strategy."); + lookup_strategy = new ParentConsistentHash(this); + break; + default: + ink_release_assert(0); + } + return config_parse_error::ok(); } @@ -932,9 +679,6 @@ ParentRecord::UpdateMatch(ParentResult *result, RequestData *rdata) ParentRecord::~ParentRecord() { - if (chash) { - delete chash; - } ats_free(parents); } @@ -946,6 +690,7 @@ ParentRecord::Print() printf(" %s:%d ", parents[i].hostname, parents[i].port); } printf(" rr=%s direct=%s\n", ParentRRStr[round_robin], (go_direct == true) ? "true" : "false"); + printf(" parent_is_proxy=%s\n", ((parent_is_proxy == true) ? "true" : "false")); } // ParentRecord* createDefaultParent(char* val) @@ -1018,7 +763,6 @@ setup_socks_servers(ParentRecord *rec_arr, int len) return 0; } - void SocksServerConfig::reconfigure() { @@ -1026,11 +770,13 @@ SocksServerConfig::reconfigure() int retry_time = 30; int fail_threshold; - ParentConfigParams *params; - params = new ParentConfigParams; + ParentConfigParams *params = NULL; // Allocate parent table - params->ParentTable = new P_table("proxy.config.socks.socks_config_file", "[Socks Server Selection]", &socks_server_tags); + 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 PARENT_ReadConfigStringAlloc(default_val, "proxy.config.socks.default_servers"); @@ -1039,24 +785,24 @@ SocksServerConfig::reconfigure() if (params->DefaultParent) setup_socks_servers(params->DefaultParent, 1); - if (params->ParentTable->ipMatch) - setup_socks_servers(params->ParentTable->ipMatch->data_array, params->ParentTable->ipMatch->array_len); + if (params->parent_table->ipMatch) + setup_socks_servers(params->parent_table->ipMatch->data_array, params->parent_table->ipMatch->array_len); // Handle parent timeout PARENT_ReadConfigInteger(retry_time, "proxy.config.socks.server_retry_time"); - params->ParentRetryTime = retry_time; + params->c_params->ParentRetryTime = retry_time; // Handle parent enable // enable is always true for use. We will come here only if socks is enabled - params->ParentEnable = 1; + params->c_params->ParentEnable = 1; // Handle the fail threshold PARENT_ReadConfigInteger(fail_threshold, "proxy.config.socks.server_fail_threshold"); - params->FailThreshold = fail_threshold; + params->c_params->FailThreshold = fail_threshold; // Handle dns parent only // PARENT_ReadConfigInteger(dns_parent_only, dns_parent_only_var); - params->DNS_ParentOnly = 0; + params->c_params->DNS_ParentOnly = 0; m_id = configProcessor.set(m_id, params); @@ -1071,7 +817,8 @@ SocksServerConfig::print() ParentConfigParams *params = SocksServerConfig::acquire(); printf("Parent Selection Config for Socks Server\n"); - printf("\tEnabled %d\tRetryTime %d\tParent DNS Only %d\n", params->ParentEnable, params->ParentRetryTime, params->DNS_ParentOnly); + printf("\tEnabled %d\tRetryTime %d\tParent DNS Only %d\n", params->c_params->ParentEnable, params->c_params->ParentRetryTime, + params->c_params->DNS_ParentOnly); if (params->DefaultParent == NULL) { printf("\tNo Default Parent\n"); } else { @@ -1079,7 +826,7 @@ SocksServerConfig::print() params->DefaultParent->Print(); } printf(" "); - params->ParentTable->Print(); + params->parent_table->Print(); SocksServerConfig::release(params); } @@ -1119,18 +866,20 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // first, set everything up *pstatus = REGRESSION_TEST_INPROGRESS; ParentConfig config; - ParentConfigParams *params = new ParentConfigParams(); - params->FailThreshold = 1; - params->ParentRetryTime = 5; + ParentConfigParams *params; + P_table *ParentTable; passes = fails = 0; config.startup(); - params->ParentEnable = true; char tbl[2048]; #define T(x) ink_strlcat(tbl, x, sizeof(tbl)); -#define REBUILD \ - 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); \ - params->ParentTable->BuildTableFromString(tbl); +#define REBUILD \ + 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->c_params->FailThreshold = 1; \ + params->c_params->ParentEnable = true; \ + params->c_params->ParentRetryTime = 5; HttpRequestData *request = NULL; ParentResult *result = NULL; #define REINIT \ @@ -1316,7 +1065,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, } // sleep(5); // parents should come back up; they don't - sleep(params->ParentRetryTime + 1); + sleep(params->c_params->ParentRetryTime + 1); // Fix: The following tests failed because // br() should set xact_start correctly instead of 0. @@ -1386,6 +1135,11 @@ show_result(ParentResult *p) printf("hostname is %s\n", p->hostname); printf("port is %d\n", p->port); break; + case PARENT_ORIGIN: + printf("result is PARENT_ORIGIN\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; diff --git a/proxy/ParentSelection.h b/proxy/ParentSelection.h index a74b659c90b..ad76c2aa31e 100644 --- a/proxy/ParentSelection.h +++ b/proxy/ParentSelection.h @@ -35,19 +35,19 @@ #include "ProxyConfig.h" #include "ControlBase.h" #include "ControlMatcher.h" - #include "P_RecProcess.h" - -#include "ts/ink_platform.h" #include "ts/ConsistentHash.h" +#include "ts/Tokenizer.h" +#include "ts/ink_apidefs.h" #define MAX_PARENTS 64 struct RequestData; - struct matcher_line; struct ParentResult; class ParentRecord; +class ParentSelectionBase; +class ParentSelectionStrategy; enum ParentResultType { PARENT_UNDEFINED, @@ -55,25 +55,98 @@ enum ParentResultType { PARENT_SPECIFIED, PARENT_AGENT, PARENT_FAIL, + PARENT_ORIGIN, +}; + +enum ParentRR_t { + P_NO_ROUND_ROBIN = 0, + P_STRICT_ROUND_ROBIN, + P_HASH_ROUND_ROBIN, + P_CONSISTENT_HASH, +}; + +// struct pRecord +// +// A record for an invidual parent +// +struct pRecord : ATSConsistentHashNode { + char hostname[MAXDNAME + 1]; + int port; + time_t failedAt; + int failCount; + int32_t upAt; + const char *scheme; // for which parent matches (if any) + int idx; + float weight; }; typedef ControlMatcher P_table; +// class ParentRecord : public ControlBase // -// API to outside world +// A record for a configuration line in the parent.config +// file // +class ParentRecord : public ControlBase +{ +public: + ParentRecord() + : parents(NULL), secondary_parents(NULL), num_parents(0), num_secondary_parents(0), round_robin(P_NO_ROUND_ROBIN), + ignore_query(false), rr_next(0), go_direct(true), parent_is_proxy(true), lookup_strategy(NULL) + { + } + + ~ParentRecord(); + + config_parse_error Init(matcher_line *line_info); + bool DefaultInit(char *val); + void UpdateMatch(ParentResult *result, RequestData *rdata); + void Print(); + pRecord *parents; + pRecord *secondary_parents; + int num_parents; + int num_secondary_parents; + + bool + bypass_ok() const + { + return go_direct; + } + bool + isParentProxy() const + { + return parent_is_proxy; + } + + const char *scheme; + // private: + const char *ProcessParents(char *val, bool isPrimary); + ParentRR_t round_robin; + bool ignore_query; + volatile uint32_t rr_next; + bool go_direct; + bool parent_is_proxy; + ParentSelectionStrategy *lookup_strategy; +}; + +// If the parent was set by the external customer api, +// our HttpRequestData structure told us what parent to +// use and we are only called to preserve clean interface +// between HttpTransact & the parent selection code. The following +ParentRecord *const extApiRecord = (ParentRecord *)0xeeeeffff; + struct ParentResult { ParentResult() - : r(PARENT_UNDEFINED), hostname(NULL), port(0), line_number(0), epoch(NULL), rec(NULL), last_parent(0), start_parent(0), - wrap_around(false), retry(false) + : r(PARENT_UNDEFINED), hostname(NULL), port(0), retry(false), line_number(0), epoch(NULL), rec(NULL), last_parent(0), + start_parent(0), wrap_around(false), last_lookup(0) { - memset(foundParents, 0, sizeof(foundParents)); - }; + } // For outside consumption ParentResultType r; const char *hostname; int port; + bool retry; // Internal use only // Not to be modified by HTTP @@ -83,144 +156,132 @@ struct ParentResult { uint32_t last_parent; uint32_t start_parent; bool wrap_around; - bool retry; - // Arena *a; - ATSConsistentHashIter chashIter; - bool foundParents[MAX_PARENTS]; + int last_lookup; // state for for consistent hash. }; -class HttpRequestData; - -struct ParentConfigParams : public ConfigInfo { - ParentConfigParams(); - ~ParentConfigParams(); - - // void findParent(RequestData* rdata, ParentResult* result) +// +// API definition. +class ParentSelectionStrategy +{ +public: + // void lookupParent(bool firstCall, ParentResult *result, RequestData *rdata) // - // Does initial parent lookup + // The implementation parent lookup. // - inkcoreapi void findParent(HttpRequestData *rdata, ParentResult *result); + virtual void lookupParent(bool firstCall, ParentResult *result, RequestData *rdata) = 0; // void markParentDown(ParentResult* rsult) // // Marks the parent pointed to by result as down // - inkcoreapi void markParentDown(ParentResult *result); + virtual void markParentDown(ParentResult *result) = 0; - // void recordRetrySuccess + // uint32_t numParents(ParentResult *result); // - // After a successful retry, http calls this function - // to clear the bits indicating the parent is down + // Returns the number of parent records in a strategy. // - void recordRetrySuccess(ParentResult *result); + virtual uint32_t numParents(ParentResult *result) = 0; - // void nextParent(RequestData* rdata, ParentResult* result); - // - // Marks the parent pointed to by result as down and attempts - // to find the next parent + // void recordRetrySuccess // - inkcoreapi void nextParent(HttpRequestData *rdata, ParentResult *result); - - // bool parentExists(HttpRequestData* rdata) + // After a successful retry, http calls this function + // to clear the bits indicating the parent is down // - // Returns true if there is a parent matching the request data and - // false otherwise - bool parentExists(HttpRequestData *rdata); + virtual void recordRetrySuccess(ParentResult *result) = 0; - // bool apiParentExists(HttpRequestData* rdata) - // - // Retures true if a parent has been set through the api - bool apiParentExists(HttpRequestData *rdata); + // virtual destructor. + virtual ~ParentSelectionStrategy() {}; +}; - P_table *ParentTable; - ParentRecord *DefaultParent; +struct config_params { int32_t ParentRetryTime; int32_t ParentEnable; int32_t FailThreshold; int32_t DNS_ParentOnly; }; -struct ParentConfig { +// +// Implements common functionality of the ParentSelection. +// +class ParentSelectionBase +{ public: - static void startup(); - static void reconfigure(); - static void print(); - - inkcoreapi static ParentConfigParams * - acquire() + ParentRecord *parent_record; + struct config_params *c_params; + ParentSelectionBase(); + ~ParentSelectionBase() { - return (ParentConfigParams *)configProcessor.get(ParentConfig::m_id); + if (c_params) + delete c_params; } - inkcoreapi static void - release(ParentConfigParams *params) +}; + +class ParentConfigParams : public ParentSelectionStrategy, public ConfigInfo +{ +public: + P_table *parent_table; + ParentRecord *DefaultParent; + struct config_params *c_params; + ParentConfigParams(P_table *_parent_table); + ~ParentConfigParams(){}; + + bool apiParentExists(HttpRequestData *rdata); + void findParent(HttpRequestData *rdata, ParentResult *result); + void nextParent(HttpRequestData *rdata, ParentResult *result); + bool parentExists(HttpRequestData *rdata); + + // implementation of functions from ParentSelectionStrategy. + void + lookupParent(bool firstCall, ParentResult *result, RequestData *rdata) { - configProcessor.release(ParentConfig::m_id, params); + ink_release_assert(result->rec->lookup_strategy != NULL); + return result->rec->lookup_strategy->lookupParent(firstCall, result, rdata); } + void + markParentDown(ParentResult *result) + { + ink_release_assert(result->rec->lookup_strategy != NULL); + result->rec->lookup_strategy->markParentDown(result); + } - static int m_id; -}; -// -// End API to outside world -// - + uint32_t + numParents(ParentResult *result) + { + ink_release_assert(result->rec->lookup_strategy != NULL); + return result->rec->lookup_strategy->numParents(result); + } -// struct pRecord -// -// A record for an invidual parent -// -struct pRecord : ATSConsistentHashNode { - char hostname[MAXDNAME + 1]; - int port; - time_t failedAt; - int failCount; - int32_t upAt; - const char *scheme; // for which parent matches (if any) - int idx; - float weight; + void + recordRetrySuccess(ParentResult *result) + { + ink_release_assert(result != NULL); + result->rec->lookup_strategy->recordRetrySuccess(result); + } }; -enum ParentRR_t { - P_NO_ROUND_ROBIN = 0, - P_STRICT_ROUND_ROBIN, - P_HASH_ROUND_ROBIN, - P_CONSISTENT_HASH, -}; +class HttpRequestData; -// class ParentRecord : public ControlBase -// -// A record for a configuration line in the parent.config -// file -// -class ParentRecord : public ControlBase -{ +struct ParentConfig { public: - ParentRecord() : parents(NULL), num_parents(0), round_robin(P_NO_ROUND_ROBIN), rr_next(0), go_direct(true), chash(NULL) {} - - ~ParentRecord(); + static void startup(); + static void reconfigure(); + static void print(); + static void set_parent_table(P_table *pTable, ParentRecord *rec, int num_elements); - config_parse_error Init(matcher_line *line_info); - bool DefaultInit(char *val); - void UpdateMatch(ParentResult *result, RequestData *rdata); - void FindParent(bool firstCall, ParentResult *result, RequestData *rdata, ParentConfigParams *config); - void Print(); - pRecord *parents; - int num_parents; + static ParentConfigParams * + acquire() + { + return (ParentConfigParams *)configProcessor.get(ParentConfig::m_id); + } - bool - bypass_ok() const + static void + release(ParentConfigParams *strategy) { - return go_direct; + configProcessor.release(ParentConfig::m_id, strategy); } - const char *scheme; - // private: - const char *ProcessParents(char *val); - void buildConsistentHash(void); - ParentRR_t round_robin; - volatile uint32_t rr_next; - bool go_direct; - ATSConsistentHash *chash; + static int m_id; }; // Helper Functions diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc index 5a94fcba5eb..91662626780 100644 --- a/proxy/http/HttpConfig.cc +++ b/proxy/http/HttpConfig.cc @@ -1117,6 +1117,15 @@ HttpConfig::startup() // Local Manager HttpEstablishStaticConfigLongLong(c.synthetic_port, "proxy.config.admin.synthetic_port"); + // parent origin. + HttpEstablishStaticConfigLongLong(c.oride.simple_retry_enabled, "proxy.config.http.parent_origin.simple_retry_enabled"); + HttpEstablishStaticConfigStringAlloc(c.oride.simple_retry_response_codes_string, + "proxy.config.http.parent_origin.simple_retry_response_codes"); + + HttpEstablishStaticConfigLongLong(c.oride.dead_server_retry_enabled, "proxy.config.http.parent_origin.dead_server_retry_enabled"); + HttpEstablishStaticConfigStringAlloc(c.oride.dead_server_retry_response_codes_string, + "proxy.config.http.parent_origin.dead_server_retry_response_codes"); + // Cluster time delta gets it own callback since it needs // to use ink_atomic_swap c.cluster_time_delta = 0; @@ -1321,6 +1330,7 @@ HttpConfig::reconfigure() params->connect_ports_string = ats_strdup(m_master.connect_ports_string); params->connect_ports = parse_ports_list(params->connect_ports_string); + params->response_codes = new ResponseCodes(); params->oride.request_hdr_max_size = m_master.oride.request_hdr_max_size; params->oride.response_hdr_max_size = m_master.oride.response_hdr_max_size; @@ -1378,6 +1388,12 @@ HttpConfig::reconfigure() // Local Manager params->synthetic_port = m_master.synthetic_port; + // simple and dead server retry. + params->oride.simple_retry_enabled = m_master.oride.simple_retry_enabled; + params->oride.dead_server_retry_enabled = m_master.oride.dead_server_retry_enabled; + params->oride.simple_retry_response_codes_string = m_master.oride.simple_retry_response_codes_string; + params->oride.dead_server_retry_response_codes_string = m_master.oride.dead_server_retry_response_codes_string; + m_id = configProcessor.set(m_id, params); #undef INT_TO_BOOL @@ -1486,6 +1502,27 @@ HttpConfig::parse_ports_list(char *ports_string) return (ports_list); } +/////////////////////////////////////////////////////// +// ResponseCodes implementation. +// /////////////////////////////////////////////////// +bool +ResponseCodes::contains(int code, MgmtString r_codes) +{ + char *c = r_codes, *p = NULL; + + do { + if (atoi(c) == code) { + return true; + } + p = strchr(c, ','); + if (p != NULL) { + c = (p + 1); + } + } while (p != NULL); + + return false; +} + //////////////////////////////////////////////////////////////// // // HttpConfig::parse_url_expansions() diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h index 5f99cbdeaf2..2f4ce107311 100644 --- a/proxy/http/HttpConfig.h +++ b/proxy/http/HttpConfig.h @@ -355,6 +355,13 @@ struct HttpConfigPortRange { } }; +class ResponseCodes +{ +public: + ResponseCodes(){}; + bool contains(int, MgmtString); +}; + ///////////////////////////////////////////////////////////// // This is a little helper class, used by the HttpConfigParams // and State (txn) structure. It allows for certain configs @@ -384,12 +391,13 @@ struct OverridableHttpConfigParams { freshness_fuzz_min_time(0), max_cache_open_read_retries(-1), cache_open_read_retry_time(10), cache_generation_number(-1), max_cache_open_write_retries(1), background_fill_active_timeout(60), http_chunking_size(4096), flow_high_water_mark(0), flow_low_water_mark(0), default_buffer_size_index(8), default_buffer_water_mark(32768), slow_log_threshold(0), - + parent_connect_attempts(4), per_parent_connect_attempts(2), simple_retry_enabled(0), dead_server_retry_enabled(0), // Strings / floats must come last body_factory_template_base(NULL), body_factory_template_base_len(0), proxy_response_server_string(NULL), proxy_response_server_string_len(0), global_user_agent_header(NULL), global_user_agent_header_size(0), cache_heuristic_lm_factor(0.10), freshness_fuzz_prob(0.005), background_fill_threshold(0.5), cache_open_write_fail_action(0), - redirection_enabled(0), redirect_use_orig_cache_key(0), number_of_redirections(1) + redirection_enabled(0), redirect_use_orig_cache_key(0), number_of_redirections(1), simple_retry_response_codes_string(NULL), + dead_server_retry_response_codes_string(NULL) { } @@ -561,6 +569,19 @@ struct OverridableHttpConfigParams { MgmtInt default_buffer_size_index; MgmtInt default_buffer_water_mark; MgmtInt slow_log_threshold; + + //////////////////////////////////// + // origin server connect attempts // + //////////////////////////////////// + MgmtInt parent_connect_attempts; + MgmtInt per_parent_connect_attempts; + + /////////////////////////////////////////////////// + // parent origin server load balancing variables // + /////////////////////////////////////////////////// + MgmtInt simple_retry_enabled; + MgmtInt dead_server_retry_enabled; + // IMPORTANT: Here comes all strings / floats configs. /////////////////////////////////////////////////////////////////// @@ -595,6 +616,14 @@ struct OverridableHttpConfigParams { MgmtByte redirection_enabled; MgmtByte redirect_use_orig_cache_key; MgmtInt number_of_redirections; + + //################################################## + //# + //# simple and dead server retry response codes. + //# + //################################################## + MgmtString simple_retry_response_codes_string; + MgmtString dead_server_retry_response_codes_string; }; @@ -700,6 +729,11 @@ struct HttpConfigParams : public ConfigInfo { char *connect_ports_string; HttpConfigPortRange *connect_ports; + /////////////////////////////////////////////////////// + // simple retry and dead server retry response codes.// + // /////////////////////////////////////////////////// + ResponseCodes *response_codes; + ////////// // Push // ////////// diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index 188aef5589b..cc3efbf03eb 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -363,7 +363,7 @@ HttpSM::init() // Added to skip dns if the document is in cache. DNS will be forced if there is a ip based ACL in // cache control or parent.config or if the doc_in_cache_skip_dns is disabled or if http caching is disabled // TODO: This probably doesn't honor this as a per-transaction overridable config. - t_state.force_dns = (ip_rule_in_CacheControlTable() || t_state.parent_params->ParentTable->ipMatch || + t_state.force_dns = (ip_rule_in_CacheControlTable() || t_state.parent_params->parent_table->ipMatch || !(t_state.txn_conf->doc_in_cache_skip_dns) || !(t_state.txn_conf->cache_http)); http_parser.m_allow_non_http = t_state.http_config_param->parser_allow_non_http; diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index 36b3767556d..edb6bfb9499 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -209,6 +209,7 @@ find_server_and_update_current_info(HttpTransact::State *s) int host_len; const char *host = s->hdr_info.client_request.host_get(&host_len); + DebugTxn("http_trans", "starting find_server_and_update_current_info()"); if (ptr_len_cmp(host, host_len, local_host_ip_str, sizeof(local_host_ip_str) - 1) == 0) { // Do not forward requests to local_host onto a parent. // I just wanted to do this for cop heartbeats, someone else @@ -232,6 +233,7 @@ find_server_and_update_current_info(HttpTransact::State *s) s->parent_params->findParent(&s->request_data, &s->parent_result); break; case PARENT_SPECIFIED: + case PARENT_ORIGIN: s->parent_params->nextParent(&s->request_data, &s->parent_result); // Hack! @@ -250,7 +252,7 @@ find_server_and_update_current_info(HttpTransact::State *s) // 2) the config permits us // 3) the config permitted us to dns the origin server if (!s->parent_params->apiParentExists(&s->request_data) && s->parent_result.rec->bypass_ok() && - s->http_config_param->no_dns_forward_to_parent == 0) { + s->http_config_param->no_dns_forward_to_parent == 0 && s->parent_result.rec->isParentProxy()) { s->parent_result.r = PARENT_DIRECT; } break; @@ -266,14 +268,15 @@ find_server_and_update_current_info(HttpTransact::State *s) } switch (s->parent_result.r) { + case PARENT_ORIGIN: case PARENT_SPECIFIED: s->parent_info.name = s->arena.str_store(s->parent_result.hostname, strlen(s->parent_result.hostname)); update_current_info(&s->current, &s->parent_info, HttpTransact::PARENT_PROXY, (s->current.attempts)++); update_dns_info(&s->dns_info, &s->current, 0, &s->arena); ink_assert(s->dns_info.looking_up == HttpTransact::PARENT_PROXY); s->next_hop_scheme = URL_WKSIDX_HTTP; - return HttpTransact::PARENT_PROXY; + case PARENT_FAIL: // No more parents - need to return an error message s->current.request_to = HttpTransact::HOST_NONE; @@ -413,7 +416,7 @@ how_to_open_connection(HttpTransact::State *s) break; } - if (s->method == HTTP_WKSIDX_CONNECT && s->parent_result.r != PARENT_SPECIFIED) { + if (s->method == HTTP_WKSIDX_CONNECT && (s->parent_result.r != PARENT_SPECIFIED || s->parent_result.r != PARENT_ORIGIN)) { s->cdn_saved_next_action = HttpTransact::SM_ACTION_ORIGIN_SERVER_RAW_OPEN; } else { s->cdn_saved_next_action = HttpTransact::SM_ACTION_ORIGIN_SERVER_OPEN; @@ -1381,7 +1384,7 @@ HttpTransact::HandleRequest(State *s) // if the newly added varible doc_in_cache_skip_dns is not enabled if (s->dns_info.lookup_name[0] <= '9' && s->dns_info.lookup_name[0] >= '0' && (!s->state_machine->enable_redirection || !s->redirect_info.redirect_in_process) && - s->parent_params->ParentTable->hostMatch) { + s->parent_params->parent_table->hostMatch) { s->force_dns = 1; } /* A redirect means we need to check some things again. @@ -1793,7 +1796,7 @@ HttpTransact::OSDNSLookup(State *s) // we've come back after already trying the server to get a better address // and finished with all backtracking - return to trying the server. TRANSACT_RETURN(how_to_open_connection(s), HttpTransact::HandleResponse); - } else if (s->dns_info.lookup_name[0] <= '9' && s->dns_info.lookup_name[0] >= '0' && s->parent_params->ParentTable->hostMatch && + } else if (s->dns_info.lookup_name[0] <= '9' && s->dns_info.lookup_name[0] >= '0' && s->parent_params->parent_table->hostMatch && !s->http_config_param->no_dns_forward_to_parent) { // note, broken logic: ACC fudges the OR stmt to always be true, // 'AuthHttpAdapter' should do the rev-dns if needed, not here . @@ -2689,6 +2692,21 @@ HttpTransact::HandleCacheOpenReadHit(State *s) update_current_info(&s->current, NULL, UNDEFINED_LOOKUP, 0); DebugTxn("http_trans", "CacheOpenReadHit - server_down, returning stale document"); } + // This case results if Parent Selection cannot find an available parent and + // go_direct is false. In this case, return the cached response if we can, otherwise + // send a 502 (handle_parent_died()). + else if (s->current.request_to == HOST_NONE && s->parent_result.r == PARENT_FAIL) { + if (is_server_negative_cached(s) && response_returnable == true && + is_stale_cache_response_returnable(s) == true) { + server_up = false; + update_current_info(&s->current, NULL, UNDEFINED_LOOKUP, 0); + DebugTxn("http_trans", "CacheOpenReadHit - server_down, returning stale document"); + } + else { + handle_parent_died(s); + return; + } + } } if (server_up || s->stale_icp_lookup) { @@ -3125,6 +3143,11 @@ HttpTransact::HandleCacheOpenReadMiss(State *s) if (!h->is_cache_control_set(HTTP_VALUE_ONLY_IF_CACHED)) { find_server_and_update_current_info(s); + // parent_result.r could come back set as PARENT_FAIL, need to check this. + if (s->parent_result.r == PARENT_FAIL) { + handle_parent_died(s); + return; + } if (!s->current.server->dst_addr.isValid()) { ink_release_assert(s->current.request_to == PARENT_PROXY || s->http_config_param->no_dns_forward_to_parent != 0); if (s->current.request_to == PARENT_PROXY) { @@ -3550,23 +3573,53 @@ HttpTransact::handle_response_from_parent(State *s) return; } - if (s->current.attempts < s->http_config_param->parent_connect_attempts) { + // try a simple retry if we received a simple retryable response from the parent. + if (s->current.retry_type == SIMPLE_RETRY || s->current.retry_type == DEAD_SERVER_RETRY) { + if (s->current.retry_type == SIMPLE_RETRY) { + if (s->current.simple_retry_attempts >= (int)s->parent_params->numParents(&s->parent_result) - 1) { + DebugTxn("http_trans", "SIMPLE_RETRY: retried all parents, send error to client.\n"); + s->current.retry_type = UNDEFINED_RETRY; + } else { + s->current.simple_retry_attempts++; + DebugTxn("http_trans", "SIMPLE_RETRY: try another parent.\n"); + s->current.retry_type = UNDEFINED_RETRY; + next_lookup = find_server_and_update_current_info(s); + } + } else { // DEAD_SERVER_RETRY + if (s->current.dead_server_retry_attempts >= (int)s->parent_params->numParents(&s->parent_result) - 1) { + DebugTxn("http_trans", "DEAD_SERVER_RETRY: retried all parents, send error to client.\n"); + s->current.retry_type = UNDEFINED_RETRY; + } else { + s->current.dead_server_retry_attempts++; + DebugTxn("http_trans", "DEAD_SERVER_RETRY: marking parent down and trying another.\n"); + s->current.retry_type = UNDEFINED_RETRY; + s->parent_params->markParentDown(&s->parent_result); + next_lookup = find_server_and_update_current_info(s); + } + } + } + // parent_connect_attempts is set from proxy.config.http.parent_proxy.total_connect_attempts in + // records.config. + else if (s->current.attempts < s->txn_conf->parent_connect_attempts) { s->current.attempts++; // Are we done with this particular parent? - if ((s->current.attempts - 1) % s->http_config_param->per_parent_connect_attempts != 0) { + // per_parent_connect_attempts should be less than proxy.config.http.parent_proxy.total_connect_attempts + // so that a new parent is tried. + if ((s->current.attempts - 1) % s->txn_conf->per_parent_connect_attempts != 0) { // No we are not done with this parent so retry s->next_action = how_to_open_connection(s); DebugTxn("http_trans", "%s Retrying parent for attempt %d, max %" PRId64, "[handle_response_from_parent]", - s->current.attempts, s->http_config_param->per_parent_connect_attempts); + s->current.attempts, s->txn_conf->per_parent_connect_attempts); return; } else { - DebugTxn("http_trans", "%s %d per parent attempts exhausted", "[handle_response_from_parent]", s->current.attempts); + DebugTxn("http_trans", "%s %d per parent attempts exhausted, s->current.state: %d", "[handle_response_from_parent]", + s->current.attempts, s->current.state); // Only mark the parent down if we failed to connect // to the parent otherwise slow origin servers cause // us to mark the parent down - if (s->current.state == CONNECTION_ERROR) { + if (s->current.state != ACTIVE_TIMEOUT && s->current.state != CONNECTION_ALIVE && s->current.state != CONNECTION_CLOSED) { s->parent_params->markParentDown(&s->parent_result); } // We are done so look for another parent if any @@ -6438,6 +6491,8 @@ HttpTransact::is_request_retryable(State *s) bool HttpTransact::is_response_valid(State *s, HTTPHdr *incoming_response) { + int server_response = 0; + if (s->current.state != CONNECTION_ALIVE) { ink_assert((s->current.state == CONNECTION_ERROR) || (s->current.state == OPEN_RAW_ERROR) || (s->current.state == PARSE_ERROR) || (s->current.state == CONNECTION_CLOSED) || @@ -6448,6 +6503,42 @@ HttpTransact::is_response_valid(State *s, HTTPHdr *incoming_response) return false; } + // is this response is from a load balanced parent. + if (s->current.request_to == PARENT_PROXY && s->parent_result.r == PARENT_ORIGIN) { + server_response = http_hdr_status_get(s->hdr_info.server_response.m_http); + DebugTxn("http_trans", "[is_response_valid] server_response = %d, simple_retry_attempts: %d, numParents:%d \n", server_response, + s->current.simple_retry_attempts, s->parent_params->numParents(&s->parent_result)); + // is a simple retry required. + if (s->txn_conf->simple_retry_enabled && + (s->current.simple_retry_attempts < (int)s->parent_params->numParents(&s->parent_result) - 1) && + s->http_config_param->response_codes->contains(server_response, s->txn_conf->simple_retry_response_codes_string)) { + DebugTxn("parent_select", "GOT A SIMPLE RETRY RESPONSE"); + // initiate a retry if we have not already tried all parents, otherwise the response is sent to the client as is. + // see SIMPLE_RETRY in handle_response_from_parent(). + if (s->current.simple_retry_attempts < (int)s->parent_params->numParents(&s->parent_result)) { + s->current.state = BAD_INCOMING_RESPONSE; + s->current.retry_type = SIMPLE_RETRY; + } else { + DebugTxn("http_trans", "SIMPLE_RETRY: retried all parents, send error to client.\n"); + } + } + // is a dead server retry required. + else if (s->txn_conf->dead_server_retry_enabled && + (s->current.dead_server_retry_attempts < (int)s->parent_params->numParents(&s->parent_result) - 1) && + s->http_config_param->response_codes->contains(server_response, + s->txn_conf->dead_server_retry_response_codes_string)) { + DebugTxn("parent_select", "GOT A DEAD_SERVER RETRY RESPONSE"); + // initiate a dead server retry if we have not already tried all parents, otherwise the response is sent to the client as is. + // see DEAD_SERVER_RETRY in handle_response_from_parent(). + if (s->current.dead_server_retry_attempts < (int)s->parent_params->numParents(&s->parent_result)) { + s->current.state = BAD_INCOMING_RESPONSE; + s->current.retry_type = DEAD_SERVER_RETRY; + } else { + DebugTxn("http_trans", "DEAD_SERVER_RETRY: retried all parents, send error to client.\n"); + } + } + } + s->hdr_info.response_error = check_response_validity(s, incoming_response); switch (s->hdr_info.response_error) { @@ -7802,6 +7893,13 @@ HttpTransact::build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_r // don't have a host anywhere. outgoing_request->set_url_target_from_host_field(); } + // In this case, the parent is actually the origin. We utilized parent selection + // to pick a load balanced origin server using round robin, or consistent hash. + else if (s->current.request_to == PARENT_PROXY && !s->parent_result.rec->isParentProxy() && + outgoing_request->is_target_in_url()) { + DebugTxn("http_trans", "[build_request] removing target from URL for parent origin"); + HttpTransactHeaders::remove_host_name_from_url(outgoing_request); + } // If the response is most likely not cacheable, eg, request with Authorization, // do we really want to remove conditional headers to get large 200 response? diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h index c5a91000ba4..5e1c3e2a1d6 100644 --- a/proxy/http/HttpTransact.h +++ b/proxy/http/HttpTransact.h @@ -351,6 +351,8 @@ class HttpTransact HTTP_TRANSACT_MAGIC_SEPARATOR = 0x12345678 }; + enum ParentOriginRetry_t { UNDEFINED_RETRY, SIMPLE_RETRY, DEAD_SERVER_RETRY }; + enum LookingUp_t { ORIGIN_SERVER, UNDEFINED_LOOKUP, @@ -704,9 +706,13 @@ class HttpTransact ink_time_t now; ServerState_t state; int attempts; + int simple_retry_attempts; + int dead_server_retry_attempts; + ParentOriginRetry_t retry_type; _CurrentInfo() - : mode(UNDEFINED_MODE), request_to(UNDEFINED_LOOKUP), server(NULL), now(0), state(STATE_UNDEFINED), attempts(1){}; + : mode(UNDEFINED_MODE), request_to(UNDEFINED_LOOKUP), server(NULL), now(0), state(STATE_UNDEFINED), attempts(1), + simple_retry_attempts(0), dead_server_retry_attempts(0), retry_type(UNDEFINED_RETRY){}; } CurrentInfo; typedef struct _DNSLookupInfo { @@ -829,23 +835,23 @@ class HttpTransact bool cdn_remap_complete; bool first_dns_lookup; - bool backdoor_request; // internal - bool cop_test_page; // internal - HttpRequestData request_data; ParentConfigParams *parent_params; ParentResult parent_result; + HttpRequestData request_data; CacheControlResult cache_control; CacheLookupResult_t cache_lookup_result; // FilterResult content_control; + bool backdoor_request; // internal + bool cop_test_page; // internal StateMachineAction_t next_action; // out StateMachineAction_t api_next_action; // out void (*transact_return_point)(HttpTransact::State *s); // out // We keep this so we can jump back to the upgrade handler after remap is complete + bool is_upgrade_request; void (*post_remap_upgrade_return_point)(HttpTransact::State *s); // out const char *upgrade_token_wks; - bool is_upgrade_request; // Some WebSocket state bool is_websocket; @@ -971,24 +977,23 @@ class HttpTransact is_revalidation_necessary(false), request_will_not_selfloop(false), // YTS Team, yamsat source(SOURCE_NONE), pre_transform_source(SOURCE_NONE), req_flavor(REQ_FLAVOR_FWDPROXY), pending_work(NULL), cdn_saved_next_action(SM_ACTION_UNDEFINED), cdn_saved_transact_return_point(NULL), cdn_remap_complete(false), - first_dns_lookup(true), backdoor_request(false), cop_test_page(false), parent_params(NULL), - cache_lookup_result(CACHE_LOOKUP_NONE), next_action(SM_ACTION_UNDEFINED), api_next_action(SM_ACTION_UNDEFINED), - transact_return_point(NULL), post_remap_upgrade_return_point(NULL), upgrade_token_wks(NULL), is_upgrade_request(false), - is_websocket(false), did_upgrade_succeed(false), internal_msg_buffer(NULL), internal_msg_buffer_type(NULL), - internal_msg_buffer_size(0), internal_msg_buffer_fast_allocator_size(-1), icp_lookup_success(false), scheme(-1), - next_hop_scheme(scheme), orig_scheme(scheme), method(0), cause_of_death_errno(-UNKNOWN_INTERNAL_ERROR), - client_request_time(UNDEFINED_TIME), request_sent_time(UNDEFINED_TIME), response_received_time(UNDEFINED_TIME), - plugin_set_expire_time(UNDEFINED_TIME), state_machine_id(0), first_stats(), current_stats(NULL), - client_connection_enabled(true), acl_filtering_performed(false), negative_caching(false), srv_lookup(false), - www_auth_content(CACHE_AUTH_NONE), remap_plugin_instance(0), fp_tsremap_os_response(NULL), - http_return_code(HTTP_STATUS_NONE), api_txn_active_timeout_value(-1), api_txn_connect_timeout_value(-1), - api_txn_dns_timeout_value(-1), api_txn_no_activity_timeout_value(-1), cache_req_hdr_heap_handle(NULL), - cache_resp_hdr_heap_handle(NULL), api_cleanup_cache_read(false), api_server_response_no_store(false), - api_server_response_ignore(false), api_http_sm_shutdown(false), api_modifiable_cached_resp(false), - api_server_request_body_set(false), api_req_cacheable(false), api_resp_cacheable(false), api_server_addr_set(false), - stale_icp_lookup(false), api_update_cached_object(UPDATE_CACHED_OBJECT_NONE), api_lock_url(LOCK_URL_FIRST), - saved_update_next_action(SM_ACTION_UNDEFINED), saved_update_cache_action(CACHE_DO_UNDEFINED), url_map(), - pCongestionEntry(NULL), congest_saved_next_action(SM_ACTION_UNDEFINED), congestion_control_crat(0), + first_dns_lookup(true), parent_params(NULL), cache_lookup_result(CACHE_LOOKUP_NONE), backdoor_request(false), + cop_test_page(false), next_action(SM_ACTION_UNDEFINED), api_next_action(SM_ACTION_UNDEFINED), transact_return_point(NULL), + is_upgrade_request(false), post_remap_upgrade_return_point(NULL), upgrade_token_wks(NULL), is_websocket(false), + did_upgrade_succeed(false), internal_msg_buffer(NULL), internal_msg_buffer_type(NULL), internal_msg_buffer_size(0), + internal_msg_buffer_fast_allocator_size(-1), icp_lookup_success(false), scheme(-1), next_hop_scheme(scheme), + orig_scheme(scheme), method(0), cause_of_death_errno(-UNKNOWN_INTERNAL_ERROR), client_request_time(UNDEFINED_TIME), + request_sent_time(UNDEFINED_TIME), response_received_time(UNDEFINED_TIME), plugin_set_expire_time(UNDEFINED_TIME), + state_machine_id(0), first_stats(), current_stats(NULL), client_connection_enabled(true), acl_filtering_performed(false), + negative_caching(false), srv_lookup(false), www_auth_content(CACHE_AUTH_NONE), remap_plugin_instance(0), + fp_tsremap_os_response(NULL), http_return_code(HTTP_STATUS_NONE), api_txn_active_timeout_value(-1), + api_txn_connect_timeout_value(-1), api_txn_dns_timeout_value(-1), api_txn_no_activity_timeout_value(-1), + cache_req_hdr_heap_handle(NULL), cache_resp_hdr_heap_handle(NULL), api_cleanup_cache_read(false), + api_server_response_no_store(false), api_server_response_ignore(false), api_http_sm_shutdown(false), + api_modifiable_cached_resp(false), api_server_request_body_set(false), api_req_cacheable(false), api_resp_cacheable(false), + api_server_addr_set(false), stale_icp_lookup(false), api_update_cached_object(UPDATE_CACHED_OBJECT_NONE), + api_lock_url(LOCK_URL_FIRST), saved_update_next_action(SM_ACTION_UNDEFINED), saved_update_cache_action(CACHE_DO_UNDEFINED), + url_map(), pCongestionEntry(NULL), congest_saved_next_action(SM_ACTION_UNDEFINED), congestion_control_crat(0), congestion_congested_or_failed(0), congestion_connection_opened(0), filter_mask(0), remap_redirect(NULL), reverse_proxy(false), url_remap_success(false), api_skip_all_remapping(false), already_downgraded(false), pristine_url(), range_setup(RANGE_NONE), num_range_fields(0), range_output_cl(0), ranges(NULL), txn_conf(NULL),