From f96b7346173197d3f084b00af4eff6fd621530ef Mon Sep 17 00:00:00 2001 From: Robert Butts Date: Mon, 1 Feb 2021 14:38:05 -0700 Subject: [PATCH 1/2] Add plugin symbols necessary for parent selection --- include/ts/apidefs.h.in | 20 +++ include/ts/nexthop.h | 49 -------- include/ts/parentselectdefs.h | 60 +++++++++ include/ts/ts.h | 28 +++++ iocore/cache/test/stub.cc | 2 +- iocore/utils/I_Machine.h | 1 + iocore/utils/Machine.cc | 15 ++- proxy/HostStatus.h | 17 +-- proxy/ParentConsistentHash.cc | 30 ++--- proxy/ParentRoundRobin.cc | 14 +-- proxy/ParentSelection.cc | 56 ++++----- proxy/ParentSelection.h | 2 +- proxy/http/HttpTransact.cc | 115 ++++++++++++------ proxy/http/HttpTransact.h | 12 ++ proxy/http/remap/NextHopConsistentHash.cc | 26 ++-- proxy/http/remap/NextHopRoundRobin.cc | 14 +-- proxy/http/remap/NextHopSelectionStrategy.cc | 2 +- proxy/http/remap/NextHopSelectionStrategy.h | 11 +- .../remap/unit-tests/nexthop_test_stubs.cc | 22 ++-- .../unit-tests/test_NextHopConsistentHash.cc | 4 +- src/traffic_server/HostStatus.cc | 93 +++++++------- src/traffic_server/InkAPI.cc | 63 ++++++++++ 22 files changed, 424 insertions(+), 232 deletions(-) delete mode 100644 include/ts/nexthop.h create mode 100644 include/ts/parentselectdefs.h mode change 100644 => 100755 proxy/http/HttpTransact.h diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index e85958db57b..6af3ceafa23 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1303,6 +1303,26 @@ extern tsapi const char *const TS_PROTO_TAG_IPV6; */ #define TS_NULL_MLOC ((TSMLoc)0) +/* -------------------------------------------------------------------------- + HostStatus types */ + +typedef enum { + TS_HOST_STATUS_INIT, + TS_HOST_STATUS_DOWN, + TS_HOST_STATUS_UP, +} TSHostStatus; + +/* MUST match proxy/HostStatus.h Reason. + * If a value is added here, it MUST be added there with the same value. + */ +typedef enum { + TS_HOST_STATUS_ACTIVE = 0x1, + TS_HOST_STATUS_LOCAL = 0x2, + TS_HOST_STATUS_MANUAL = 0x4, + TS_HOST_STATUS_SELF_DETECT = 0x8, + TS_HOST_STATUS_ALL = 0xf, +} TSHostStatusReason; + /* -------------------------------------------------------------------------- Interface for the UUID APIs. https://www.ietf.org/rfc/rfc4122.txt. */ typedef enum { diff --git a/include/ts/nexthop.h b/include/ts/nexthop.h deleted file mode 100644 index ed623805649..00000000000 --- a/include/ts/nexthop.h +++ /dev/null @@ -1,49 +0,0 @@ -/** @file - - Traffic Server SDK API header file - - @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. - - @section developers Developers - - NextHop plugin interface. - - */ - -#pragma once - -#include - -// plugin callback commands. -enum NHCmd { NH_MARK_UP, NH_MARK_DOWN }; - -struct NHHealthStatus { - virtual bool isNextHopAvailable(TSHttpTxn txn, const char *hostname, const int port, void *ih = nullptr) = 0; - virtual void markNextHop(TSHttpTxn txn, const char *hostname, const int port, const NHCmd status, void *ih = nullptr, - const time_t now = 0) = 0; - virtual ~NHHealthStatus() {} -}; - -struct NHPluginStrategy { - virtual void findNextHop(TSHttpTxn txnp, void *ih = nullptr) = 0; - virtual bool nextHopExists(TSHttpTxn txnp, void *ih = nullptr) = 0; - virtual ~NHPluginStrategy() {} - - NHHealthStatus *healthStatus; -}; diff --git a/include/ts/parentselectdefs.h b/include/ts/parentselectdefs.h new file mode 100644 index 00000000000..021b91858af --- /dev/null +++ b/include/ts/parentselectdefs.h @@ -0,0 +1,60 @@ +/** @file + + Traffic Server SDK API header file + + @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. + + @section developers Developers + + NextHop plugin interface. + + */ + +#pragma once + +#include +#include + +// Plugins may set this to indicate how to retry. +// +// If handled is false, then no plugin set it, and Core will proceed to do its own thing. +// +// If handled is true, core will not do any parent processing, markdown, or anything else, +// but will use the values in this for whether to use the existing response or make another request, +// and what that request should look like. +// +// See the API functions which take this for ownership requirements of pointers, like hostname. +// +// hostname is the hostname to use for the next request. It must be null-terminated. +// hostname_len is the length of hostname, not including the terminating null. +// +typedef struct { + // TODO this shouldn't be necessary - plugins should manipulate the response as they see fit, + // core shouldn't "know" if it was a "success" or "failure," only the response or retry data/action. + // But for now, core needs to know, for reasons. + const char *hostname; + size_t hostname_len; + in_port_t port; + bool fail; + bool is_retry; + bool nextHopExists; + bool responseIsRetryable; + bool goDirect; + bool parentIsProxy; +} TSResponseAction; diff --git a/include/ts/ts.h b/include/ts/ts.h index 7015e34b182..cb8a54aa5dc 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -30,6 +30,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -2601,6 +2602,12 @@ tsapi TSReturnCode TSRemapFromUrlGet(TSHttpTxn txnp, TSMLoc *urlLocp); // tsapi TSReturnCode TSRemapToUrlGet(TSHttpTxn txnp, TSMLoc *urlLocp); +// Override response behavior, and hard-set the state machine for whether to succeed or fail, and how. +tsapi void TSHttpTxnResponseActionSet(TSHttpTxn txnp, TSResponseAction *action); + +// Get the overridden response behavior set by previously called plugins. +tsapi void TSHttpTxnResponseActionGet(TSHttpTxn txnp, TSResponseAction *action); + /* * Get a TSIOBufferReader to read the buffered body. The return value needs to be freed. */ @@ -2641,6 +2648,27 @@ tsapi TSReturnCode TSHttpTxnClientStreamIdGet(TSHttpTxn txnp, uint64_t *stream_i */ tsapi TSReturnCode TSHttpTxnClientStreamPriorityGet(TSHttpTxn txnp, TSHttpPriority *priority); +/* + * Returns TS_SUCCESS if hostname is this machine, as used for parent and remap self-detection. + * Returns TS_ERROR if hostname is not this machine. + */ +tsapi TSReturnCode TSHostnameIsSelf(const char *hostname, size_t hostname_len); + +/* + * Gets the status of hostname in the outparam status, and the status reason in the outparam reason. + * The reason is a logical-or combination of the reasons in TSHostStatusReason. + * If either outparam is null, it will not be set and no error will be returned. + * Returns TS_SUCCESS if the hostname was a parent and existed in the HostStatus, else TS_ERROR. + */ +tsapi TSReturnCode TSHostStatusGet(const char *hostname, const size_t hostname_len, TSHostStatus *status, unsigned int *reason); + +/* + * Sets the status of hostname in status, down_time, and reason. + * The reason is a logical-or combination of the reasons in TSHostStatusReason. + */ +tsapi void TSHostStatusSet(const char *hostname, const size_t hostname_len, TSHostStatus status, const unsigned int down_time, + const unsigned int reason); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/iocore/cache/test/stub.cc b/iocore/cache/test/stub.cc index 65f81e79c05..dcc232c1a2c 100644 --- a/iocore/cache/test/stub.cc +++ b/iocore/cache/test/stub.cc @@ -157,7 +157,7 @@ ts::svtoi(TextView src, TextView *out, int base) } void -HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned int down_time, const unsigned int reason) +HostStatus::setHostStatus(const char *name, TSHostStatus status, const unsigned int down_time, const unsigned int reason) { } diff --git a/iocore/utils/I_Machine.h b/iocore/utils/I_Machine.h index 4c28cc594e0..c744cd78cf4 100644 --- a/iocore/utils/I_Machine.h +++ b/iocore/utils/I_Machine.h @@ -83,6 +83,7 @@ struct Machine { /// @return The global instance of this class. static self *instance(); bool is_self(const char *name); + bool is_self(const char *name, int name_len); bool is_self(const IpAddr *ipaddr); bool is_self(struct sockaddr const *addr); void insert_id(char *id); diff --git a/iocore/utils/Machine.cc b/iocore/utils/Machine.cc index bcfc1efbe6d..ddb7942f0cb 100644 --- a/iocore/utils/Machine.cc +++ b/iocore/utils/Machine.cc @@ -32,9 +32,8 @@ #endif static void -make_to_lower_case(const char *name, char *lower_case_name, int buf_len) +make_to_lower_case(const char *name, int name_len, char *lower_case_name, int buf_len) { - int name_len = strlen(name); int i; if (name_len > (buf_len - 1)) { @@ -238,14 +237,20 @@ Machine::~Machine() bool Machine::is_self(const char *name) +{ + return is_self(name, strlen(name)); +} + +bool +Machine::is_self(const char *name, int name_len) { char lower_case_name[TS_MAX_HOST_NAME_LEN + 1] = {0}; - if (name == nullptr) { + if (name_len == 0) { return false; } - make_to_lower_case(name, lower_case_name, sizeof(lower_case_name)); + make_to_lower_case(name, name_len, lower_case_name, sizeof(lower_case_name)); return machine_id_strings.find(lower_case_name) != machine_id_strings.end(); } @@ -279,7 +284,7 @@ Machine::insert_id(char *id) { char lower_case_name[TS_MAX_HOST_NAME_LEN + 1] = {0}; - make_to_lower_case(id, lower_case_name, sizeof(lower_case_name)); + make_to_lower_case(id, strlen(id), lower_case_name, sizeof(lower_case_name)); machine_id_strings.emplace(lower_case_name); } diff --git a/proxy/HostStatus.h b/proxy/HostStatus.h index 2cc18130a7c..8649fe97eb0 100644 --- a/proxy/HostStatus.h +++ b/proxy/HostStatus.h @@ -41,12 +41,6 @@ // host_status stats prefix. static const std::string stat_prefix = "proxy.process.host_status."; -enum HostStatus_t { - HOST_STATUS_INIT, - HOST_STATUS_DOWN, - HOST_STATUS_UP, -}; - static const constexpr char *HostStatusNames[3] = {"HOST_STATUS_INIT", "HOST_STATUS_DOWN", "HOST_STATUS_UP"}; static const constexpr char *ReasonStatus[2] = {"UP", "DOWN"}; @@ -97,7 +91,7 @@ struct Reason { // host status POD struct HostStatRec { - HostStatus_t status; + TSHostStatus status; unsigned int reasons; // time the host was marked down for a given reason. time_t active_marked_down; @@ -186,11 +180,12 @@ struct HostStatus { static HostStatus instance; return instance; } - void setHostStatus(const char *name, const HostStatus_t status, const unsigned int down_time, const unsigned int reason); - HostStatRec *getHostStatus(const char *name); - void createHostStat(const char *name, const char *data = nullptr); + void setHostStatus(const std::string_view name, const TSHostStatus status, const unsigned int down_time, + const unsigned int reason); + HostStatRec *getHostStatus(const std::string_view name); + void createHostStat(const std::string_view name, const char *data = nullptr); void loadHostStatusFromStats(); - void loadRecord(std::string &name, HostStatRec &h); + void loadRecord(std::string_view name, HostStatRec &h); RecErrT getHostStat(std::string &stat_name, char *buf, unsigned int buf_len); private: diff --git a/proxy/ParentConsistentHash.cc b/proxy/ParentConsistentHash.cc index 3c00c3f6ec4..a68344f3b4e 100644 --- a/proxy/ParentConsistentHash.cc +++ b/proxy/ParentConsistentHash.cc @@ -143,7 +143,7 @@ ParentConsistentHash::selectParent(bool first_call, ParentResult *result, Reques uint32_t last_lookup; pRecord *prtmp = nullptr, *pRec = nullptr; HostStatus &pStatus = HostStatus::instance(); - HostStatus_t host_stat = HostStatus_t::HOST_STATUS_INIT; + TSHostStatus host_stat = TSHostStatus::TS_HOST_STATUS_INIT; Debug("parent_select", "ParentConsistentHash::%s(): Using a consistent hash parent selection strategy.", __func__); ink_assert(numParents(result) > 0 || result->rec->go_direct == true); @@ -174,7 +174,7 @@ ParentConsistentHash::selectParent(bool first_call, ParentResult *result, Reques last_lookup = PRIMARY; break; case 3: - if (result->first_choice_status == HOST_STATUS_DOWN && chash[SECONDARY] != nullptr) { + if (result->first_choice_status == TS_HOST_STATUS_DOWN && chash[SECONDARY] != nullptr) { last_lookup = SECONDARY; } else { last_lookup = PRIMARY; @@ -215,23 +215,23 @@ ParentConsistentHash::selectParent(bool first_call, ParentResult *result, Reques // didn't find a parent or the parent is marked unavailable or the parent is marked down HostStatRec *hst = (pRec) ? pStatus.getHostStatus(pRec->hostname) : nullptr; - host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP; + host_stat = (hst) ? hst->status : TSHostStatus::TS_HOST_STATUS_UP; if (firstCall) { result->first_choice_status = host_stat; } // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason // ignore the down status and mark it as avaialble - if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) { + if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == TS_HOST_STATUS_DOWN)) { if (hst->reasons == Reason::SELF_DETECT) { - host_stat = HOST_STATUS_UP; + host_stat = TS_HOST_STATUS_UP; } } - if (!pRec || (pRec && !pRec->available.load()) || host_stat == HOST_STATUS_DOWN) { + if (!pRec || (pRec && !pRec->available.load()) || host_stat == TS_HOST_STATUS_DOWN) { do { // check if the host is retryable. It's retryable if the retry window has elapsed // and the global host status is HOST_STATUS_UP - if (pRec && !pRec->available.load() && host_stat == HOST_STATUS_UP) { + if (pRec && !pRec->available.load() && host_stat == TS_HOST_STATUS_UP) { Debug("parent_select", "Parent.failedAt = %u, retry = %u, xact_start = %u", static_cast(pRec->failedAt.load()), static_cast(retry_time), static_cast(request_info->xact_start)); if ((pRec->failedAt.load() + retry_time) < request_info->xact_start) { @@ -262,7 +262,7 @@ ParentConsistentHash::selectParent(bool first_call, ParentResult *result, Reques } break; case 3: - if (result->first_choice_status == HOST_STATUS_DOWN) { + if (result->first_choice_status == TS_HOST_STATUS_DOWN) { if (chash[SECONDARY] != nullptr && !wrap_around[SECONDARY]) { last_lookup = SECONDARY; } else if (!wrap_around[PRIMARY]) { @@ -304,15 +304,15 @@ ParentConsistentHash::selectParent(bool first_call, ParentResult *result, Reques break; } hst = (pRec) ? pStatus.getHostStatus(pRec->hostname) : nullptr; - host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP; + host_stat = (hst) ? hst->status : TSHostStatus::TS_HOST_STATUS_UP; // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason // ignore the down status and mark it as avaialble - if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) { + if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == TS_HOST_STATUS_DOWN)) { if (hst->reasons == Reason::SELF_DETECT) { - host_stat = HOST_STATUS_UP; + host_stat = TS_HOST_STATUS_UP; } } - } while (!pRec || !pRec->available.load() || host_stat == HOST_STATUS_DOWN); + } while (!pRec || !pRec->available.load() || host_stat == TS_HOST_STATUS_DOWN); } Debug("parent_select", "Additional parent lookups: %d", lookups); @@ -323,12 +323,12 @@ ParentConsistentHash::selectParent(bool first_call, ParentResult *result, Reques // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason // ignore the down status and mark it as avaialble - if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) { + if ((pRec && result->rec->ignore_self_detect) && (hst && hst->status == TS_HOST_STATUS_DOWN)) { if (hst->reasons == Reason::SELF_DETECT) { - host_stat = HOST_STATUS_UP; + host_stat = TS_HOST_STATUS_UP; } } - if (pRec && host_stat == HOST_STATUS_UP && (pRec->available.load() || result->retry)) { + if (pRec && host_stat == TS_HOST_STATUS_UP && (pRec->available.load() || result->retry)) { result->result = PARENT_SPECIFIED; result->hostname = pRec->hostname; result->port = pRec->port; diff --git a/proxy/ParentRoundRobin.cc b/proxy/ParentRoundRobin.cc index cb13b8f60ce..86e1b30c43f 100644 --- a/proxy/ParentRoundRobin.cc +++ b/proxy/ParentRoundRobin.cc @@ -63,7 +63,7 @@ ParentRoundRobin::selectParent(bool first_call, ParentResult *result, RequestDat bool parentUp = false; bool parentRetry = false; HostStatus &pStatus = HostStatus::instance(); - HostStatus_t host_stat = HostStatus_t::HOST_STATUS_UP; + TSHostStatus host_stat = TSHostStatus::TS_HOST_STATUS_UP; HttpRequestData *request_info = static_cast(rdata); @@ -137,18 +137,18 @@ ParentRoundRobin::selectParent(bool first_call, ParentResult *result, RequestDat // should be retried do { HostStatRec *hst = pStatus.getHostStatus(parents[cur_index].hostname); - host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP; + host_stat = (hst) ? hst->status : TSHostStatus::TS_HOST_STATUS_UP; // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason // ignore the down status and mark it as avaialble - if (result->rec->ignore_self_detect && (hst && hst->status == HOST_STATUS_DOWN)) { + if (result->rec->ignore_self_detect && (hst && hst->status == TS_HOST_STATUS_DOWN)) { if (hst->reasons == Reason::SELF_DETECT) { - host_stat = HOST_STATUS_UP; + host_stat = TS_HOST_STATUS_UP; } } Debug("parent_select", "cur_index: %d, result->start_parent: %d", cur_index, result->start_parent); // DNS ParentOnly inhibits bypassing the parent so always return that t if ((parents[cur_index].failedAt.load() == 0) || (parents[cur_index].failCount.load() < static_cast(fail_threshold))) { - if (host_stat == HOST_STATUS_UP) { + if (host_stat == TS_HOST_STATUS_UP) { Debug("parent_select", "FailThreshold = %d", fail_threshold); Debug("parent_select", "Selecting a parent due to little failCount (faileAt: %u failCount: %d)", (unsigned)parents[cur_index].failedAt.load(), parents[cur_index].failCount.load()); @@ -156,7 +156,7 @@ ParentRoundRobin::selectParent(bool first_call, ParentResult *result, RequestDat } } else { if ((result->wrap_around) || - (((parents[cur_index].failedAt + retry_time) < request_info->xact_start) && host_stat == HOST_STATUS_UP)) { + (((parents[cur_index].failedAt + retry_time) < request_info->xact_start) && host_stat == TS_HOST_STATUS_UP)) { if (parents[cur_index].retriers.fetch_add(1, std::memory_order_relaxed) < max_retriers) { Debug("parent_select", "Parent[%d].failedAt = %u, retry = %u, retriers = %d, max_retriers = %u, xact_start = %" PRId64 " but wrap = %d", @@ -175,7 +175,7 @@ ParentRoundRobin::selectParent(bool first_call, ParentResult *result, RequestDat } } - if (parentUp == true && host_stat != HOST_STATUS_DOWN) { + if (parentUp == true && host_stat != TS_HOST_STATUS_DOWN) { Debug("parent_select", "status for %s: %d", parents[cur_index].hostname, host_stat); result->result = PARENT_SPECIFIED; result->hostname = parents[cur_index].hostname; diff --git a/proxy/ParentSelection.cc b/proxy/ParentSelection.cc index 3d38dcd268c..1f60afa3ea2 100644 --- a/proxy/ParentSelection.cc +++ b/proxy/ParentSelection.cc @@ -401,7 +401,7 @@ ParentRecord::PreProcessParents(const char *val, const int line_num, char *buf, continue; } else { Debug("parent_select", "token: %s, matches this machine. Marking down self from parent list at line %d", fqdn, line_num); - hs.setHostStatus(fqdn, HostStatus_t::HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); + hs.setHostStatus(fqdn, TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); } } } else { @@ -413,7 +413,7 @@ ParentRecord::PreProcessParents(const char *val, const int line_num, char *buf, } else { Debug("parent_select", "token: %s, matches this machine. Marking down self from parent list at line %d", token, line_num); - hs.setHostStatus(token, HostStatus_t::HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); + hs.setHostStatus(token, TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); } } } @@ -1117,11 +1117,11 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // from records.snap as DOWN due to previous testing. // HostStatus &_st = HostStatus::instance(); - _st.setHostStatus("furry", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("fluffy", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("frisky", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("fuzzy", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("curly", HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("furry", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fluffy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("frisky", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("curly", TS_HOST_STATUS_UP, 0, Reason::MANUAL); // Test 1 SET_MAX_RETRIERS(20); @@ -1508,7 +1508,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // Test 184 // mark fuzzy down with HostStatus API. - _st.setHostStatus("fuzzy", HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); ST(184); REINIT; @@ -1519,7 +1519,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // Test 185 // mark fluffy down and expect furry to be chosen - _st.setHostStatus("fluffy", HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("fluffy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); ST(185); REINIT; @@ -1530,9 +1530,9 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // Test 186 // mark furry and frisky down, fuzzy up and expect fuzzy to be chosen - _st.setHostStatus("furry", HOST_STATUS_DOWN, 0, Reason::MANUAL); - _st.setHostStatus("frisky", HOST_STATUS_DOWN, 0, Reason::MANUAL); - _st.setHostStatus("fuzzy", HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("furry", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("frisky", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); ST(186); REINIT; @@ -1550,10 +1550,10 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, REBUILD; // mark all up. - _st.setHostStatus("furry", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("fluffy", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("frisky", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("fuzzy", HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("furry", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fluffy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("frisky", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); REINIT; br(request, "i.am.rabbit.net"); @@ -1563,7 +1563,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // Test 188 // mark fuzzy down and expect fluffy. - _st.setHostStatus("fuzzy", HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); ST(188); REINIT; @@ -1574,7 +1574,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // Test 189 // mark fuzzy back up and expect fuzzy. - _st.setHostStatus("fuzzy", HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); ST(189); REINIT; @@ -1590,7 +1590,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // because the host status is set to down. params->markParentDown(result, fail_threshold, retry_time); // set host status down - _st.setHostStatus("fuzzy", HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); // sleep long enough so that fuzzy is retryable sleep(params->policy.ParentRetryTime + 1); ST(190); @@ -1601,7 +1601,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // now set the host status on fuzzy to up and it should now // be retried. - _st.setHostStatus("fuzzy", HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); ST(191); REINIT; br(request, "i.am.rabbit.net"); @@ -1614,10 +1614,10 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, T("dest_domain=rabbit.net parent=fuzzy:80,fluffy:80,furry:80,frisky:80 round_robin=false go_direct=true\n"); REBUILD; // mark all up. - _st.setHostStatus("fuzzy", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("fluffy", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("furry", HOST_STATUS_UP, 0, Reason::MANUAL); - _st.setHostStatus("frisky", HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fluffy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("furry", TS_HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("frisky", TS_HOST_STATUS_UP, 0, Reason::MANUAL); // fuzzy should be chosen. sleep(1); REINIT; @@ -1632,7 +1632,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, sleep(params->policy.ParentRetryTime + 1); // since the host status is down even though fuzzy is // retryable, fluffy should be chosen - _st.setHostStatus("fuzzy", HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); REINIT; br(request, "i.am.rabbit.net"); FP; @@ -1642,7 +1642,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, // set the host status for fuzzy back up and since its // retryable fuzzy should be chosen ST(194); - _st.setHostStatus("fuzzy", HOST_STATUS_UP, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_UP, 0, Reason::MANUAL); REINIT; br(request, "i.am.rabbit.net"); FP; @@ -1763,7 +1763,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, T("dest_domain=rabbit.net parent=fuzzy:80|1.0;fluffy:80|1.0 secondary_parent=furry:80|1.0;frisky:80|1.0 " "round_robin=consistent_hash go_direct=false secondary_mode=3\n"); REBUILD; - _st.setHostStatus("fuzzy", HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("fuzzy", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); REINIT; br(request, "i.am.rabbit.net"); FP; @@ -1834,7 +1834,7 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, "round_robin=consistent_hash go_direct=false\n"); REBUILD; REINIT; - _st.setHostStatus("curly", HOST_STATUS_DOWN, 0, Reason::MANUAL); + _st.setHostStatus("curly", TS_HOST_STATUS_DOWN, 0, Reason::MANUAL); br(request, "i.am.stooges.net"); FP; RE(verify(result, PARENT_SPECIFIED, "carol", 80), 211); diff --git a/proxy/ParentSelection.h b/proxy/ParentSelection.h index a9b894786ce..efc4759d086 100644 --- a/proxy/ParentSelection.h +++ b/proxy/ParentSelection.h @@ -188,7 +188,7 @@ struct ParentResult { int port; bool retry; bool chash_init[MAX_GROUP_RINGS] = {false}; - HostStatus_t first_choice_status = HostStatus_t::HOST_STATUS_INIT; + TSHostStatus first_choice_status = TSHostStatus::TS_HOST_STATUS_INIT; void reset() diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index ebeb43c5c0c..f1257627b7d 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -21,7 +21,7 @@ limitations under the License. */ -#include "ts/nexthop.h" +#include "ts/parentselectdefs.h" #include "tscore/ink_platform.h" #include @@ -94,16 +94,16 @@ extern HttpBodyFactory *body_factory; inline static bool bypass_ok(HttpTransact::State *s) { - bool r = false; url_mapping *mp = s->url_map.getMapping(); - - if (mp && mp->strategy) { + if (s->response_action.handled) { + return s->response_action.action.goDirect; + } else if (mp && mp->strategy) { // remap strategies do not support the TSHttpTxnParentProxySet API. - r = mp->strategy->go_direct; + return mp->strategy->go_direct; } else if (s->parent_params) { - r = s->parent_result.bypass_ok(); + return s->parent_result.bypass_ok(); } - return r; + return false; } // wrapper to choose between a remap next hop strategy or use parent.config @@ -150,15 +150,15 @@ numParents(HttpTransact::State *s) inline static bool parent_is_proxy(HttpTransact::State *s) { - bool r = false; url_mapping *mp = s->url_map.getMapping(); - - if (mp && mp->strategy) { - r = mp->strategy->parent_is_proxy; + if (s->response_action.handled) { + return s->response_action.action.parentIsProxy; + } else if (mp && mp->strategy) { + return mp->strategy->parent_is_proxy; } else if (s->parent_params) { - r = s->parent_result.parent_is_proxy(); + return s->parent_result.parent_is_proxy(); } - return r; + return false; } // wrapper to get the parent.config retry type. @@ -178,12 +178,22 @@ inline static void findParent(HttpTransact::State *s) { url_mapping *mp = s->url_map.getMapping(); - - if (mp && mp->strategy) { - return mp->strategy->findNextHop(reinterpret_cast(s->state_machine)); + if (s->response_action.handled) { + s->parent_result.hostname = s->response_action.action.hostname; + s->parent_result.port = s->response_action.action.port; + s->parent_result.retry = s->response_action.action.is_retry; + if (!s->response_action.action.fail) { + s->parent_result.result = PARENT_SPECIFIED; + } else if (s->response_action.action.goDirect) { + s->parent_result.result = PARENT_DIRECT; + } else { + s->parent_result.result = PARENT_FAIL; + } + } else if (mp && mp->strategy) { + mp->strategy->findNextHop(reinterpret_cast(s->state_machine)); } else if (s->parent_params) { - return s->parent_params->findParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold, - s->txn_conf->parent_retry_time); + s->parent_params->findParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold, + s->txn_conf->parent_retry_time); } } @@ -195,11 +205,13 @@ markParentDown(HttpTransact::State *s) HTTP_INCREMENT_DYN_STAT(http_total_parent_marked_down_count); url_mapping *mp = s->url_map.getMapping(); - if (mp && mp->strategy) { - return mp->strategy->markNextHop(reinterpret_cast(s->state_machine), s->parent_result.hostname, - s->parent_result.port, NH_MARK_DOWN); + if (s->response_action.handled) { + // Do nothing. If a plugin handled the response, let it handle markdown. + } else if (mp && mp->strategy) { + mp->strategy->markNextHop(reinterpret_cast(s->state_machine), s->parent_result.hostname, s->parent_result.port, + NH_MARK_DOWN); } else if (s->parent_params) { - return s->parent_params->markParentDown(&s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time); + s->parent_params->markParentDown(&s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time); } } @@ -209,11 +221,13 @@ inline static void markParentUp(HttpTransact::State *s) { url_mapping *mp = s->url_map.getMapping(); - if (mp && mp->strategy) { - return mp->strategy->markNextHop(reinterpret_cast(s->state_machine), s->parent_result.hostname, - s->parent_result.port, NH_MARK_UP); + if (s->response_action.handled) { + // Do nothing. If a plugin handled the response, let it handle markdown + } else if (mp && mp->strategy) { + mp->strategy->markNextHop(reinterpret_cast(s->state_machine), s->parent_result.hostname, s->parent_result.port, + NH_MARK_UP); } else if (s->parent_params) { - return s->parent_params->markParentUp(&s->parent_result); + s->parent_params->markParentUp(&s->parent_result); } } @@ -223,12 +237,15 @@ inline static bool parentExists(HttpTransact::State *s) { url_mapping *mp = s->url_map.getMapping(); - if (mp && mp->strategy) { + if (s->response_action.handled) { + return s->response_action.action.nextHopExists; + } else if (mp && mp->strategy) { return mp->strategy->nextHopExists(reinterpret_cast(s->state_machine)); } else if (s->parent_params) { return s->parent_params->parentExists(&s->request_data); + } else { + return false; } - return false; } // wrapper to choose between a remap next hop strategy or use parent.config @@ -240,12 +257,23 @@ nextParent(HttpTransact::State *s) s->state_machine->sm_id, s->parent_result.hostname, HttpDebugNames::get_server_state_name(s->current.state), s->request_data.get_host()); url_mapping *mp = s->url_map.getMapping(); - if (mp && mp->strategy) { + if (s->response_action.handled) { + s->parent_result.hostname = s->response_action.action.hostname; + s->parent_result.port = s->response_action.action.port; + s->parent_result.retry = s->response_action.action.is_retry; + if (!s->response_action.action.fail) { + s->parent_result.result = PARENT_SPECIFIED; + } else if (s->response_action.action.goDirect) { + s->parent_result.result = PARENT_DIRECT; + } else { + s->parent_result.result = PARENT_FAIL; + } + } else if (mp && mp->strategy) { // NextHop only has a findNextHop() function. - return mp->strategy->findNextHop(reinterpret_cast(s->state_machine)); + mp->strategy->findNextHop(reinterpret_cast(s->state_machine)); } else if (s->parent_params) { - return s->parent_params->nextParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold, - s->txn_conf->parent_retry_time); + s->parent_params->nextParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold, + s->txn_conf->parent_retry_time); } } @@ -343,7 +371,9 @@ response_is_retryable(HttpTransact::State *s, HTTPStatus response_code) if (!HttpTransact::is_response_valid(s, &s->hdr_info.server_response) || s->current.request_to != HttpTransact::PARENT_PROXY) { return PARENT_RETRY_NONE; } - + if (s->response_action.handled) { + return s->response_action.action.responseIsRetryable ? PARENT_RETRY_SIMPLE : PARENT_RETRY_NONE; + } const url_mapping *mp = s->url_map.getMapping(); if (mp && mp->strategy) { if (mp->strategy->responseIsRetryable(s->current.simple_retry_attempts, response_code)) { @@ -628,6 +658,7 @@ find_server_and_update_current_info(HttpTransact::State *s) // if the configuration does not allow the origin to be dns'd // we're unable to go direct to the origin. if (s->http_config_param->no_dns_forward_to_parent) { + TxnDebug("http_trans", "find_server_and_update_current_info result parented, no dns, setting FAIL and HOST_NONE"); Warning("no available parents and the config proxy.config.http.no_dns_just_forward_to_parent, prevents origin lookups."); s->parent_result.result = PARENT_FAIL; return HttpTransact::HOST_NONE; @@ -1703,6 +1734,13 @@ HttpTransact::HandleApiErrorJump(State *s) return; } +// PPDNSLookupAPICall does an API callout, then calls PPDNSLookup +void +HttpTransact::PPDNSLookupAPICall(State *s) +{ + TRANSACT_RETURN(SM_ACTION_API_OS_DNS, PPDNSLookup); +} + /////////////////////////////////////////////////////////////////////////////// // Name : PPDNSLookup // Description: called after DNS lookup of parent proxy name @@ -1741,10 +1779,15 @@ HttpTransact::PPDNSLookup(State *s) if (!s->current.server->dst_addr.isValid()) { if (s->current.request_to == PARENT_PROXY) { - TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookup); + if (!s->response_action.handled) { + TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookup); + } else { + TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall); + } } else if (s->parent_result.result == PARENT_DIRECT && s->http_config_param->no_dns_forward_to_parent != 1) { // We ran out of parents but parent configuration allows us to go to Origin Server directly - return CallOSDNSLookup(s); + CallOSDNSLookup(s); + return; } else { // We could be out of parents here if all the parents failed DNS lookup ink_assert(s->current.request_to == HOST_NONE); @@ -2600,7 +2643,7 @@ HttpTransact::CallOSDNSLookup(State *s) TxnDebug("http", "[HttpTransact::callos] %s ", s->server_info.name); HostStatus &pstatus = HostStatus::instance(); HostStatRec *hst = pstatus.getHostStatus(s->server_info.name); - if (hst && hst->status == HostStatus_t::HOST_STATUS_DOWN) { + if (hst && hst->status == TSHostStatus::TS_HOST_STATUS_DOWN) { TxnDebug("http", "[HttpTransact::callos] %d ", s->cache_lookup_result); s->current.state = OUTBOUND_CONGESTION; if (s->cache_lookup_result == CACHE_LOOKUP_HIT_STALE || s->cache_lookup_result == CACHE_LOOKUP_HIT_WARNING || diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h old mode 100644 new mode 100755 index 686d8b4d998..fb5ccd0fadc --- a/proxy/http/HttpTransact.h +++ b/proxy/http/HttpTransact.h @@ -37,6 +37,7 @@ #include "Transform.h" #include "Milestones.h" #include "ts/remap.h" +#include "ts/parentselectdefs.h" #include "RemapPluginInfo.h" #include "UrlMapping.h" #include "records/I_RecHttp.h" @@ -659,6 +660,13 @@ class HttpTransact _SquidLogInfo() {} } SquidLogInfo; + typedef struct _ResponseAction { + bool handled = false; + TSResponseAction action; + + _ResponseAction() {} + } ResponseAction; + struct State { HttpTransactMagic_t m_magic = HTTP_TRANSACT_MAGIC_ALIVE; @@ -812,6 +820,8 @@ class HttpTransact bool transparent_passthrough = false; bool range_in_cache = false; + ResponseAction response_action; + // Methods void init() @@ -939,6 +949,7 @@ class HttpTransact static void OSDNSLookup(State *s); static void ReDNSRoundRobin(State *s); static void PPDNSLookup(State *s); + static void PPDNSLookupAPICall(State *s); static void OriginServerRawOpen(State *s); static void HandleCacheOpenRead(State *s); static void HandleCacheOpenReadHitFreshness(State *s); @@ -954,6 +965,7 @@ class HttpTransact static void handle_transform_ready(State *s); static void handle_transform_cache_write(State *s); static void handle_response_from_parent(State *s); + static void handle_response_from_parent_plugin(State *s); static void handle_response_from_server(State *s); static void delete_server_rr_entry(State *s, int max_retries); static void retry_server_connection_not_open(State *s, ServerState_t conn_state, unsigned max_retries); diff --git a/proxy/http/remap/NextHopConsistentHash.cc b/proxy/http/remap/NextHopConsistentHash.cc index d1d2b0ff740..be47d6d5f05 100644 --- a/proxy/http/remap/NextHopConsistentHash.cc +++ b/proxy/http/remap/NextHopConsistentHash.cc @@ -227,7 +227,7 @@ NextHopConsistentHash::findNextHop(TSHttpTxn txnp, void *ih, time_t now) HostRecord *hostRec = nullptr; std::shared_ptr pRec = nullptr; HostStatus &pStatus = HostStatus::instance(); - HostStatus_t host_stat = HostStatus_t::HOST_STATUS_INIT; + TSHostStatus host_stat = TSHostStatus::TS_HOST_STATUS_INIT; HostStatRec *hst = nullptr; if (result->line_number == -1 && result->result == PARENT_UNDEFINED) { @@ -279,7 +279,7 @@ NextHopConsistentHash::findNextHop(TSHttpTxn txnp, void *ih, time_t now) pRec = host_groups[hostRec->group_index][hostRec->host_index]; if (firstcall) { hst = (pRec) ? pStatus.getHostStatus(pRec->hostname.c_str()) : nullptr; - result->first_choice_status = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP; + result->first_choice_status = (hst) ? hst->status : TSHostStatus::TS_HOST_STATUS_UP; break; } } else { @@ -294,18 +294,18 @@ NextHopConsistentHash::findNextHop(TSHttpTxn txnp, void *ih, time_t now) // ---------------------------------------------------------------------------------------------------- hst = (pRec) ? pStatus.getHostStatus(pRec->hostname.c_str()) : nullptr; - host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP; + host_stat = (hst) ? hst->status : TSHostStatus::TS_HOST_STATUS_UP; // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason // ignore the down status and mark it as avaialble - if ((pRec && ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) { + if ((pRec && ignore_self_detect) && (hst && hst->status == TS_HOST_STATUS_DOWN)) { if (hst->reasons == Reason::SELF_DETECT) { - host_stat = HOST_STATUS_UP; + host_stat = TS_HOST_STATUS_UP; } } - if (!pRec || (pRec && !pRec->available) || host_stat == HOST_STATUS_DOWN) { + if (!pRec || (pRec && !pRec->available) || host_stat == TS_HOST_STATUS_DOWN) { do { // check if an unavailable server is now retryable, use it if it is. - if (pRec && !pRec->available && host_stat == HOST_STATUS_UP) { + if (pRec && !pRec->available && host_stat == TS_HOST_STATUS_UP) { _now == 0 ? _now = time(nullptr) : _now = now; // check if the host is retryable. It's retryable if the retry window has elapsed if ((pRec->failedAt + retry_time) < static_cast(_now)) { @@ -339,18 +339,18 @@ NextHopConsistentHash::findNextHop(TSHttpTxn txnp, void *ih, time_t now) if (hostRec) { pRec = host_groups[hostRec->group_index][hostRec->host_index]; hst = (pRec) ? pStatus.getHostStatus(pRec->hostname.c_str()) : nullptr; - host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP; + host_stat = (hst) ? hst->status : TSHostStatus::TS_HOST_STATUS_UP; // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason // ignore the down status and mark it as avaialble - if ((pRec && ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) { + if ((pRec && ignore_self_detect) && (hst && hst->status == TS_HOST_STATUS_DOWN)) { if (hst->reasons == Reason::SELF_DETECT) { - host_stat = HOST_STATUS_UP; + host_stat = TS_HOST_STATUS_UP; } } NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] Selected a new parent: %s, available: %s, wrapped: %s, lookups: %d.", sm_id, pRec->hostname.c_str(), (pRec->available) ? "true" : "false", (wrapped) ? "true" : "false", lookups); // use available host. - if (pRec->available && host_stat == HOST_STATUS_UP) { + if (pRec->available && host_stat == TS_HOST_STATUS_UP) { break; } } else { @@ -369,14 +369,14 @@ NextHopConsistentHash::findNextHop(TSHttpTxn txnp, void *ih, time_t now) } break; } - } while (!pRec || (pRec && !pRec->available) || host_stat == HOST_STATUS_DOWN); + } while (!pRec || (pRec && !pRec->available) || host_stat == TS_HOST_STATUS_DOWN); } // ---------------------------------------------------------------------------------------------------- // Validate and return the final result. // ---------------------------------------------------------------------------------------------------- - if (pRec && host_stat == HOST_STATUS_UP && (pRec->available || result->retry)) { + if (pRec && host_stat == TS_HOST_STATUS_UP && (pRec->available || result->retry)) { result->result = PARENT_SPECIFIED; result->hostname = pRec->hostname.c_str(); result->last_parent = pRec->host_index; diff --git a/proxy/http/remap/NextHopRoundRobin.cc b/proxy/http/remap/NextHopRoundRobin.cc index 0bbcc298726..3b1afe8cb15 100644 --- a/proxy/http/remap/NextHopRoundRobin.cc +++ b/proxy/http/remap/NextHopRoundRobin.cc @@ -54,7 +54,7 @@ NextHopRoundRobin::findNextHop(TSHttpTxn txnp, void *ih, time_t now) uint32_t start_host = 0; std::shared_ptr cur_host; HostStatus &pStatus = HostStatus::instance(); - HostStatus_t host_stat = HostStatus_t::HOST_STATUS_UP; + TSHostStatus host_stat = TSHostStatus::TS_HOST_STATUS_UP; if (result->line_number != -1 && result->result != PARENT_UNDEFINED) { firstcall = false; @@ -122,12 +122,12 @@ NextHopRoundRobin::findNextHop(TSHttpTxn txnp, void *ih, time_t now) // should be retried do { HostStatRec *hst = pStatus.getHostStatus(cur_host->hostname.c_str()); - host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP; + host_stat = (hst) ? hst->status : TSHostStatus::TS_HOST_STATUS_UP; // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason // ignore the down status and mark it as avaialble - if (ignore_self_detect && (hst && hst->status == HOST_STATUS_DOWN)) { + if (ignore_self_detect && (hst && hst->status == TS_HOST_STATUS_DOWN)) { if (hst->reasons == Reason::SELF_DETECT) { - host_stat = HOST_STATUS_UP; + host_stat = TS_HOST_STATUS_UP; } } @@ -138,7 +138,7 @@ NextHopRoundRobin::findNextHop(TSHttpTxn txnp, void *ih, time_t now) request_info.xact_start); // check if 'cur_host' is available, mark it up if it is. if ((cur_host->failedAt == 0) || (cur_host->failCount < fail_threshold)) { - if (host_stat == HOST_STATUS_UP) { + if (host_stat == TS_HOST_STATUS_UP) { NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] Selecting a parent, %s, due to little failCount (faileAt: %d failCount: %d), FailThreshold: %" PRIu64, @@ -149,7 +149,7 @@ NextHopRoundRobin::findNextHop(TSHttpTxn txnp, void *ih, time_t now) // available. _now == 0 ? _now = time(nullptr) : _now = now; if (((result->wrap_around) || (cur_host->failedAt + retry_time) < static_cast(_now)) && - host_stat == HOST_STATUS_UP) { + host_stat == TS_HOST_STATUS_UP) { // Reuse the parent parentUp = true; parentRetry = true; @@ -163,7 +163,7 @@ NextHopRoundRobin::findNextHop(TSHttpTxn txnp, void *ih, time_t now) cur_host->hostname.c_str(), HostStatusNames[host_stat]); // The selected host is available or retryable, return the search result. - if (parentUp == true && host_stat != HOST_STATUS_DOWN) { + if (parentUp == true && host_stat != TS_HOST_STATUS_DOWN) { NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] status for %s: %s", sm_id, cur_host->hostname.c_str(), HostStatusNames[host_stat]); result->result = PARENT_SPECIFIED; result->hostname = cur_host->hostname.c_str(); diff --git a/proxy/http/remap/NextHopSelectionStrategy.cc b/proxy/http/remap/NextHopSelectionStrategy.cc index e589c3ebc2f..e21168c312d 100644 --- a/proxy/http/remap/NextHopSelectionStrategy.cc +++ b/proxy/http/remap/NextHopSelectionStrategy.cc @@ -175,7 +175,7 @@ NextHopSelectionStrategy::Init(const YAML::Node &n) host_rec->group_index = grp; host_rec->host_index = hst; if (mach->is_self(host_rec->hostname.c_str())) { - h_stat.setHostStatus(host_rec->hostname.c_str(), HostStatus_t::HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); + h_stat.setHostStatus(host_rec->hostname.c_str(), TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); } hosts_inner.push_back(std::move(host_rec)); num_parents++; diff --git a/proxy/http/remap/NextHopSelectionStrategy.h b/proxy/http/remap/NextHopSelectionStrategy.h index d430b413669..64bf489ab66 100644 --- a/proxy/http/remap/NextHopSelectionStrategy.h +++ b/proxy/http/remap/NextHopSelectionStrategy.h @@ -23,7 +23,7 @@ #pragma once -#include "ts/nexthop.h" +#include "ts/parentselectdefs.h" #include "ParentSelection.h" #ifndef _NH_UNIT_TESTS_ @@ -42,6 +42,15 @@ namespace YAML class Node; } +enum NHCmd { NH_MARK_UP, NH_MARK_DOWN }; + +struct NHHealthStatus { + virtual bool isNextHopAvailable(TSHttpTxn txn, const char *hostname, const int port, void *ih = nullptr) = 0; + virtual void markNextHop(TSHttpTxn txn, const char *hostname, const int port, const NHCmd status, void *ih = nullptr, + const time_t now = 0) = 0; + virtual ~NHHealthStatus() {} +}; + enum NHPolicyType { NH_UNDEFINED = 0, NH_FIRST_LIVE, // first available nexthop diff --git a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc index 6eb2d7e7046..83bb44b4a0c 100644 --- a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc +++ b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc @@ -198,27 +198,27 @@ HostStatus::~HostStatus() } HostStatRec * -HostStatus::getHostStatus(const char *name) +HostStatus::getHostStatus(const std::string_view name) { - if (this->hosts_statuses[name] == nullptr) { + if (this->hosts_statuses[std::string(name)] == nullptr) { // for unit tests only, always return a record with HOST_STATUS_UP, if it wasn't set with setHostStatus static HostStatRec rec; - rec.status = HostStatus_t::HOST_STATUS_UP; + rec.status = TSHostStatus::TS_HOST_STATUS_UP; return &rec; } - return this->hosts_statuses[name]; + return this->hosts_statuses[std::string(name)]; } void -HostStatus::setHostStatus(char const *host, HostStatus_t status, unsigned int down_time, unsigned int reason) +HostStatus::setHostStatus(const std::string_view host, TSHostStatus status, unsigned int down_time, unsigned int reason) { - if (this->hosts_statuses[host] == nullptr) { - this->hosts_statuses[host] = new (HostStatRec); + if (this->hosts_statuses[std::string(host)] == nullptr) { + this->hosts_statuses[std::string(host)] = new (HostStatRec); } - this->hosts_statuses[host]->status = status; - this->hosts_statuses[host]->reasons = reason; - this->hosts_statuses[host]->local_down_time = down_time; - NH_Debug("next_hop", "setting host status for '%s' to %s", host, HostStatusNames[status]); + this->hosts_statuses[std::string(host)]->status = status; + this->hosts_statuses[std::string(host)]->reasons = reason; + this->hosts_statuses[std::string(host)]->local_down_time = down_time; + NH_Debug("next_hop", "setting host status for '%.*s' to %s", host.size(), host.data(), HostStatusNames[status]); } #include "I_UDPConnection.h" diff --git a/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc b/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc index 47660d6cbee..0be8528bd76 100644 --- a/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc +++ b/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc @@ -297,7 +297,7 @@ SCENARIO("Testing NextHop ignore_self_detect false", "[NextHopConsistentHash]") strategy = nhf.strategyInstance("ignore-self-detect-false"); HostStatus &hs = HostStatus::instance(); - hs.setHostStatus("localhost", HostStatus_t::HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); + hs.setHostStatus("localhost", TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); WHEN("the config is loaded.") { @@ -347,7 +347,7 @@ SCENARIO("Testing NextHop ignore_self_detect true", "[NextHopConsistentHash]") strategy = nhf.strategyInstance("ignore-self-detect-true"); HostStatus &hs = HostStatus::instance(); - hs.setHostStatus("localhost", HostStatus_t::HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); + hs.setHostStatus("localhost", TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); WHEN("the config is loaded.") { diff --git a/src/traffic_server/HostStatus.cc b/src/traffic_server/HostStatus.cc index 00ccb7626a4..0449aeb0a67 100644 --- a/src/traffic_server/HostStatus.cc +++ b/src/traffic_server/HostStatus.cc @@ -24,9 +24,10 @@ #include "ProcessManager.h" inline void -getStatName(std::string &stat_name, const char *name) +getStatName(std::string &stat_name, const std::string_view name) { - stat_name = stat_prefix + name; + stat_name.clear(); + stat_name.append(stat_prefix).append(name); } static void @@ -58,7 +59,7 @@ mgmt_host_status_up_callback(ts::MemSpan span) if (hs.getHostStat(stat_name, buf, 1024) == REC_ERR_FAIL) { hs.createHostStat(name); } - hs.setHostStatus(name, HostStatus_t::HOST_STATUS_UP, down_time, reason); + hs.setHostStatus(name, TSHostStatus::TS_HOST_STATUS_UP, down_time, reason); } } @@ -90,12 +91,12 @@ mgmt_host_status_down_callback(ts::MemSpan span) if (hs.getHostStat(stat_name, buf, 1024) == REC_ERR_FAIL) { hs.createHostStat(name); } - hs.setHostStatus(name, HostStatus_t::HOST_STATUS_DOWN, down_time, reason); + hs.setHostStatus(name, TSHostStatus::TS_HOST_STATUS_DOWN, down_time, reason); } } HostStatRec::HostStatRec() - : status(HOST_STATUS_UP), + : status(TS_HOST_STATUS_UP), reasons(0), active_marked_down(0), local_marked_down(0), @@ -126,9 +127,9 @@ HostStatRec::HostStatRec(std::string str) for (unsigned int i = 0; i < v1.size(); i++) { if (i == 0) { // set the status field if (v1.at(i).compare("HOST_STATUS_UP") == 0) { - status = HOST_STATUS_UP; + status = TS_HOST_STATUS_UP; } else if (v1.at(i).compare("HOST_STATUS_DOWN") == 0) { - status = HOST_STATUS_DOWN; + status = TS_HOST_STATUS_DOWN; } } else { // parse and set remaining reason fields. std::vector v2; @@ -226,13 +227,13 @@ HostStatus::loadHostStatusFromStats() } void -HostStatus::loadRecord(std::string &name, HostStatRec &h) +HostStatus::loadRecord(std::string_view name, HostStatRec &h) { HostStatRec *host_stat = nullptr; - Debug("host_statuses", "loading host status record for %s", name.c_str()); + Debug("host_statuses", "loading host status record for %.*s", int(name.size()), name.data()); ink_rwlock_wrlock(&host_status_rwlock); { - if (auto it = hosts_statuses.find(name.c_str()); it != hosts_statuses.end()) { + if (auto it = hosts_statuses.find(std::string(name)); it != hosts_statuses.end()) { host_stat = it->second; } else { host_stat = static_cast(ats_malloc(sizeof(HostStatRec))); @@ -246,7 +247,7 @@ HostStatus::loadRecord(std::string &name, HostStatRec &h) } void -HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned int down_time, const unsigned int reason) +HostStatus::setHostStatus(const std::string_view name, TSHostStatus status, const unsigned int down_time, const unsigned int reason) { std::string stat_name; char buf[1024] = {0}; @@ -260,11 +261,11 @@ HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned RecErrT result = getHostStat(stat_name, buf, 1024); // update / insert status. - // using the hash table pointer to store the HostStatus_t value. + // using the hash table pointer to store the TSHostStatus value. HostStatRec *host_stat = nullptr; ink_rwlock_wrlock(&host_status_rwlock); { - if (auto it = hosts_statuses.find(name); it != hosts_statuses.end()) { + if (auto it = hosts_statuses.find(std::string(name)); it != hosts_statuses.end()) { host_stat = it->second; } else { host_stat = static_cast(ats_malloc(sizeof(HostStatRec))); @@ -272,8 +273,8 @@ HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned hosts_statuses.emplace(name, host_stat); } if (reason & Reason::ACTIVE) { - Debug("host_statuses", "for host %s set status: %s, Reason:ACTIVE", name, HostStatusNames[status]); - if (status == HostStatus_t::HOST_STATUS_DOWN) { + Debug("host_statuses", "for host %.*s set status: %s, Reason:ACTIVE", int(name.size()), name.data(), HostStatusNames[status]); + if (status == TSHostStatus::TS_HOST_STATUS_DOWN) { host_stat->active_marked_down = time(0); host_stat->active_down_time = down_time; host_stat->reasons |= Reason::ACTIVE; @@ -286,8 +287,8 @@ HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned } } if (reason & Reason::LOCAL) { - Debug("host_statuses", "for host %s set status: %s, Reason:LOCAL", name, HostStatusNames[status]); - if (status == HostStatus_t::HOST_STATUS_DOWN) { + Debug("host_statuses", "for host %.*s set status: %s, Reason:LOCAL", int(name.size()), name.data(), HostStatusNames[status]); + if (status == TSHostStatus::TS_HOST_STATUS_DOWN) { host_stat->local_marked_down = time(0); host_stat->local_down_time = down_time; host_stat->reasons |= Reason::LOCAL; @@ -300,8 +301,8 @@ HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned } } if (reason & Reason::MANUAL) { - Debug("host_statuses", "for host %s set status: %s, Reason:MANUAL", name, HostStatusNames[status]); - if (status == HostStatus_t::HOST_STATUS_DOWN) { + Debug("host_statuses", "for host %.*s set status: %s, Reason:MANUAL", int(name.size()), name.data(), HostStatusNames[status]); + if (status == TSHostStatus::TS_HOST_STATUS_DOWN) { host_stat->manual_marked_down = time(0); host_stat->manual_down_time = down_time; host_stat->reasons |= Reason::MANUAL; @@ -314,8 +315,9 @@ HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned } } if (reason & Reason::SELF_DETECT) { - Debug("host_statuses", "for host %s set status: %s, Reason:SELF_DETECT", name, HostStatusNames[status]); - if (status == HostStatus_t::HOST_STATUS_DOWN) { + Debug("host_statuses", "for host %.*s set status: %s, Reason:SELF_DETECT", int(name.size()), name.data(), + HostStatusNames[status]); + if (status == TSHostStatus::TS_HOST_STATUS_DOWN) { host_stat->self_detect_marked_down = time(0); host_stat->reasons |= Reason::SELF_DETECT; } else { @@ -325,9 +327,9 @@ HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned } } } - if (status == HostStatus_t::HOST_STATUS_UP) { + if (status == TSHostStatus::TS_HOST_STATUS_UP) { if (host_stat->reasons == 0) { - host_stat->status = HostStatus_t::HOST_STATUS_UP; + host_stat->status = TSHostStatus::TS_HOST_STATUS_UP; } Debug("host_statuses", "reasons: %d, status: %s", host_stat->reasons, HostStatusNames[host_stat->status]); } else { @@ -342,24 +344,27 @@ HostStatus::setHostStatus(const char *name, HostStatus_t status, const unsigned std::stringstream status_rec; status_rec << *host_stat; RecSetRecordString(stat_name.c_str(), const_cast(status_rec.str().c_str()), REC_SOURCE_EXPLICIT, true); - if (status == HostStatus_t::HOST_STATUS_UP) { - Debug("host_statuses", "set status up for name: %s, status: %d, stat_name: %s", name, status, stat_name.c_str()); + if (status == TSHostStatus::TS_HOST_STATUS_UP) { + Debug("host_statuses", "set status up for name: %.*s, status: %d, stat_name: %s", int(name.size()), name.data(), status, + stat_name.c_str()); } else { - Debug("host_statuses", "set status down for name: %s, status: %d, stat_name: %s", name, status, stat_name.c_str()); + Debug("host_statuses", "set status down for name: %.*s, status: %d, stat_name: %s", int(name.size()), name.data(), status, + stat_name.c_str()); } } - Debug("host_statuses", "name: %s, status: %d", name, status); + Debug("host_statuses", "name: %.*s, status: %d", int(name.size()), name.data(), status); // log it. - if (status == HostStatus_t::HOST_STATUS_DOWN) { - Note("Host %s has been marked down, down_time: %d - %s.", name, down_time, down_time == 0 ? "indefinitely." : "seconds."); + if (status == TSHostStatus::TS_HOST_STATUS_DOWN) { + Note("Host %.*s has been marked down, down_time: %d - %s.", int(name.size()), name.data(), down_time, + down_time == 0 ? "indefinitely." : "seconds."); } else { - Note("Host %s has been marked up.", name); + Note("Host %.*s has been marked up.", int(name.size()), name.data()); } } HostStatRec * -HostStatus::getHostStatus(const char *name) +HostStatus::getHostStatus(const std::string_view name) { HostStatRec *_status = nullptr; time_t now = time(0); @@ -374,10 +379,10 @@ HostStatus::getHostStatus(const char *name) return _status; } - // the hash table value pointer has the HostStatus_t value. + // the hash table value pointer has the TSHostStatus value. ink_rwlock_rdlock(&host_status_rwlock); { - auto it = hosts_statuses.find(name); + auto it = hosts_statuses.find(std::string(name)); lookup = it != hosts_statuses.end(); if (lookup) { _status = it->second; @@ -386,29 +391,29 @@ HostStatus::getHostStatus(const char *name) ink_rwlock_unlock(&host_status_rwlock); // if the host was marked down and it's down_time has elapsed, mark it up. - if (lookup && _status->status == HostStatus_t::HOST_STATUS_DOWN) { + if (lookup && _status->status == TSHostStatus::TS_HOST_STATUS_DOWN) { unsigned int reasons = _status->reasons; if ((_status->reasons & Reason::ACTIVE) && _status->active_down_time > 0) { if ((_status->active_down_time + _status->active_marked_down) < now) { - Debug("host_statuses", "name: %s, now: %ld, down_time: %d, marked_down: %ld, reason: %s", name, now, - _status->active_down_time, _status->active_marked_down, Reason::ACTIVE_REASON); - setHostStatus(name, HostStatus_t::HOST_STATUS_UP, 0, Reason::ACTIVE); + Debug("host_statuses", "name: %.*s, now: %ld, down_time: %d, marked_down: %ld, reason: %s", int(name.size()), name.data(), + now, _status->active_down_time, _status->active_marked_down, Reason::ACTIVE_REASON); + setHostStatus(name, TSHostStatus::TS_HOST_STATUS_UP, 0, Reason::ACTIVE); reasons ^= Reason::ACTIVE; } } if ((_status->reasons & Reason::LOCAL) && _status->local_down_time > 0) { if ((_status->local_down_time + _status->local_marked_down) < now) { - Debug("host_statuses", "name: %s, now: %ld, down_time: %d, marked_down: %ld, reason: %s", name, now, - _status->local_down_time, _status->local_marked_down, Reason::LOCAL_REASON); - setHostStatus(name, HostStatus_t::HOST_STATUS_UP, 0, Reason::LOCAL); + Debug("host_statuses", "name: %.*s, now: %ld, down_time: %d, marked_down: %ld, reason: %s", int(name.size()), name.data(), + now, _status->local_down_time, _status->local_marked_down, Reason::LOCAL_REASON); + setHostStatus(name, TSHostStatus::TS_HOST_STATUS_UP, 0, Reason::LOCAL); reasons ^= Reason::LOCAL; } } if ((_status->reasons & Reason::MANUAL) && _status->manual_down_time > 0) { if ((_status->manual_down_time + _status->manual_marked_down) < now) { - Debug("host_statuses", "name: %s, now: %ld, down_time: %d, marked_down: %ld, reason: %s", name, now, - _status->manual_down_time, _status->manual_marked_down, Reason::MANUAL_REASON); - setHostStatus(name, HostStatus_t::HOST_STATUS_UP, 0, Reason::MANUAL); + Debug("host_statuses", "name: %.*s, now: %ld, down_time: %d, marked_down: %ld, reason: %s", int(name.size()), name.data(), + now, _status->manual_down_time, _status->manual_marked_down, Reason::MANUAL_REASON); + setHostStatus(name, TSHostStatus::TS_HOST_STATUS_UP, 0, Reason::MANUAL); reasons ^= Reason::MANUAL; } } @@ -419,7 +424,7 @@ HostStatus::getHostStatus(const char *name) } void -HostStatus::createHostStat(const char *name, const char *data) +HostStatus::createHostStat(const std::string_view name, const char *data) { char buf[1024] = {0}; HostStatRec r; diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc index c8246c19614..d1d409c7e15 100644 --- a/src/traffic_server/InkAPI.cc +++ b/src/traffic_server/InkAPI.cc @@ -9992,6 +9992,69 @@ TSRemapToUrlGet(TSHttpTxn txnp, TSMLoc *urlLocp) return remapUrlGet(txnp, urlLocp, &UrlMappingContainer::getToURL); } +TSReturnCode +TSHostnameIsSelf(const char *hostname, size_t hostname_len) +{ + return Machine::instance()->is_self(hostname, hostname_len) ? TS_SUCCESS : TS_ERROR; +} + +TSReturnCode +TSHostStatusGet(const char *hostname, const size_t hostname_len, TSHostStatus *status, unsigned int *reason) +{ + HostStatRec *hst = HostStatus::instance().getHostStatus(std::string_view(hostname, hostname_len)); + if (hst == nullptr) { + return TS_ERROR; + } + if (status != nullptr) { + *status = hst->status; + } + if (reason != nullptr) { + *reason = hst->reasons; + } + return TS_SUCCESS; +} + +void +TSHostStatusSet(const char *hostname, const size_t hostname_len, TSHostStatus status, const unsigned int down_time, + const unsigned int reason) +{ + HostStatus::instance().setHostStatus(std::string_view(hostname, hostname_len), status, down_time, reason); +} + +// TSHttpTxnResponseActionSet takes a ResponseAction and sets it as the behavior for finding the next parent. +// Be aware ATS will never change this outside a plugin. Therefore, plugins which set the ResponseAction +// to retry must also un-set it after the subsequent success or failure, or ATS will retry forever! +// +// The passed *action must not be null, and is copied and may be destroyed after this call returns. +// Callers must maintain owernship of action.hostname, and its lifetime must exceed the transaction. +tsapi void +TSHttpTxnResponseActionSet(TSHttpTxn txnp, TSResponseAction *action) +{ + HttpSM *sm = reinterpret_cast(txnp); + HttpTransact::State *s = &(sm->t_state); + s->response_action.handled = true; + s->response_action.action = *action; +} + +// Get the ResponseAction set by a plugin. +// +// The action is an out-param and must point to a valid location. +// The returned action.hostname must not be modified, and is owned by some plugin if not null. +// +// The action members will always be zero, if no plugin has called TSHttpTxnResponseActionSet. +// +tsapi void +TSHttpTxnResponseActionGet(TSHttpTxn txnp, TSResponseAction *action) +{ + HttpSM *sm = reinterpret_cast(txnp); + HttpTransact::State *s = &(sm->t_state); + if (!s->response_action.handled) { + memset(action, 0, sizeof(TSResponseAction)); // because {0} gives a C++ warning. Ugh. + } else { + *action = s->response_action.action; + } +} + tsapi TSIOBufferReader TSHttpTxnPostBufferReaderGet(TSHttpTxn txnp) { From 20e29bfa12915051ee56c0e2b61dadbf63ea76ff Mon Sep 17 00:00:00 2001 From: Robert Butts Date: Mon, 1 Feb 2021 14:50:31 -0700 Subject: [PATCH 2/2] Add parent_select plugin --- plugins/Makefile.am | 1 + .../experimental/parent_select/Makefile.inc | 29 + .../parent_select/consistenthash.cc | 559 ++++++++++++++++++ .../parent_select/consistenthash.h | 80 +++ .../parent_select/consistenthash_config.cc | 232 ++++++++ .../parent_select/consistenthash_config.h | 30 + .../parent_select/healthstatus.cc | 147 +++++ .../experimental/parent_select/healthstatus.h | 55 ++ .../parent_select/parent_select.cc | 460 ++++++++++++++ .../experimental/parent_select/strategy.cc | 349 +++++++++++ plugins/experimental/parent_select/strategy.h | 271 +++++++++ plugins/experimental/parent_select/util.h | 56 ++ proxy/http/HttpTransact.cc | 1 - 13 files changed, 2269 insertions(+), 1 deletion(-) create mode 100644 plugins/experimental/parent_select/Makefile.inc create mode 100644 plugins/experimental/parent_select/consistenthash.cc create mode 100644 plugins/experimental/parent_select/consistenthash.h create mode 100644 plugins/experimental/parent_select/consistenthash_config.cc create mode 100644 plugins/experimental/parent_select/consistenthash_config.h create mode 100644 plugins/experimental/parent_select/healthstatus.cc create mode 100644 plugins/experimental/parent_select/healthstatus.h create mode 100644 plugins/experimental/parent_select/parent_select.cc create mode 100644 plugins/experimental/parent_select/strategy.cc create mode 100644 plugins/experimental/parent_select/strategy.h create mode 100644 plugins/experimental/parent_select/util.h diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 52ce5d8bfdc..6d625a83932 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -66,6 +66,7 @@ include experimental/custom_redirect/Makefile.inc include experimental/fq_pacing/Makefile.inc include experimental/geoip_acl/Makefile.inc include experimental/header_freq/Makefile.inc +include experimental/parent_select/Makefile.inc include experimental/hook-trace/Makefile.inc include experimental/icap/Makefile.inc include experimental/inliner/Makefile.inc diff --git a/plugins/experimental/parent_select/Makefile.inc b/plugins/experimental/parent_select/Makefile.inc new file mode 100644 index 00000000000..e7428fe78f8 --- /dev/null +++ b/plugins/experimental/parent_select/Makefile.inc @@ -0,0 +1,29 @@ +# 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. + +pkglib_LTLIBRARIES += experimental/parent_select/parent_select.la + +experimental_parent_select_parent_select_la_SOURCES = \ + experimental/parent_select/strategy.cc \ + experimental/parent_select/healthstatus.cc \ + experimental/parent_select/consistenthash.cc \ + experimental/parent_select/consistenthash_config.cc \ + experimental/parent_select/parent_select.cc + +experimental_parent_select_parent_select_la_LDFLAGS = \ + $(AM_LDFLAGS) + +AM_CPPFLAGS += @YAMLCPP_INCLUDES@ $(TS_INCLUDES) diff --git a/plugins/experimental/parent_select/consistenthash.cc b/plugins/experimental/parent_select/consistenthash.cc new file mode 100644 index 00000000000..91daf57605f --- /dev/null +++ b/plugins/experimental/parent_select/consistenthash.cc @@ -0,0 +1,559 @@ +/* + * 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 "strategy.h" +#include "consistenthash.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "tscore/HashSip.h" +#include "tscore/ConsistentHash.h" +#include "tscore/ink_assert.h" +#include "ts/ts.h" +#include "ts/remap.h" +#include "ts/parentselectdefs.h" + +namespace +{ +const char *PLNHParentResultStr[] = {"PARENT_UNDEFINED", "PARENT_DIRECT", "PARENT_SPECIFIED", "PARENT_AGENT", "PARENT_FAIL"}; + +// hash_key strings. +constexpr std::string_view hash_key_url = "url"; +constexpr std::string_view hash_key_hostname = "hostname"; +constexpr std::string_view hash_key_path = "path"; +constexpr std::string_view hash_key_path_query = "path+query"; +constexpr std::string_view hash_key_path_fragment = "path+fragment"; +constexpr std::string_view hash_key_cache = "cache_key"; + +PLHostRecord * +chash_lookup(std::shared_ptr ring, uint64_t hash_key, ATSConsistentHashIter *iter, bool *wrapped, + ATSHash64Sip24 *hash, bool *hash_init, bool *mapWrapped, uint64_t sm_id) +{ + PLHostRecord *host_rec = nullptr; + + if (*hash_init == false) { + host_rec = static_cast(ring->lookup_by_hashval(hash_key, iter, wrapped)); + *hash_init = true; + } else { + host_rec = static_cast(ring->lookup(nullptr, iter, wrapped, hash)); + } + bool wrap_around = *wrapped; + *wrapped = *mapWrapped && *wrapped; + if (!*mapWrapped && wrap_around) { + *mapWrapped = true; + } + + return host_rec; +} + +void +chTxnToStatusTxn(PLNextHopConsistentHashTxn *txn, PLStatusTxn *statusTxn) +{ + statusTxn->result = txn->result; + statusTxn->retry = txn->retry; +} + +} // namespace + +PLNextHopConsistentHash::PLNextHopConsistentHash(const std::string_view name) : PLNextHopSelectionStrategy(name) {} + +PLNextHopConsistentHash::~PLNextHopConsistentHash() +{ + PL_NH_Debug(PL_NH_DEBUG_TAG, "destructor called for strategy named: %s", strategy_name.c_str()); +} + +#define PLUGIN_NAME "pparent_select" + +bool +PLNextHopConsistentHash::Init(const YAML::Node &n) +{ + TSDebug("pparent_select", "PLNextHopConsistentHash Init calling."); + + ATSHash64Sip24 hash; + + try { + if (n["hash_key"]) { + auto hash_key_val = n["hash_key"].Scalar(); + if (hash_key_val == hash_key_url) { + hash_key = PL_NH_URL_HASH_KEY; + } else if (hash_key_val == hash_key_hostname) { + hash_key = PL_NH_HOSTNAME_HASH_KEY; + } else if (hash_key_val == hash_key_path) { + hash_key = PL_NH_PATH_HASH_KEY; + } else if (hash_key_val == hash_key_path_query) { + hash_key = PL_NH_PATH_QUERY_HASH_KEY; + } else if (hash_key_val == hash_key_path_fragment) { + hash_key = PL_NH_PATH_FRAGMENT_HASH_KEY; + } else if (hash_key_val == hash_key_cache) { + hash_key = PL_NH_CACHE_HASH_KEY; + } else { + hash_key = PL_NH_PATH_HASH_KEY; + PL_NH_Note("Invalid 'hash_key' value, '%s', for the strategy named '%s', using default '%s'.", hash_key_val.c_str(), + strategy_name.c_str(), hash_key_path.data()); + } + } + } catch (std::exception &ex) { + TSDebug(PLUGIN_NAME, "Error parsing the strategy named '%s' due to '%s', this strategy will be ignored.", strategy_name.c_str(), + ex.what()); + + PL_NH_Note("Error parsing the strategy named '%s' due to '%s', this strategy will be ignored.", strategy_name.c_str(), + ex.what()); + return false; + } + + TSDebug(PLUGIN_NAME, "PLNextHopConsistentHash::Init strat Init calling."); + + bool result = PLNextHopSelectionStrategy::Init(n); + if (!result) { + TSDebug(PLUGIN_NAME, "PLNextHopConsistentHash::Init strat Init false."); + return false; + } + + TSDebug(PLUGIN_NAME, "PLNextHopConsistentHash::Init strat Init called."); + + // load up the hash rings. + for (uint32_t i = 0; i < groups; i++) { + std::shared_ptr hash_ring = std::make_shared(); + for (uint32_t j = 0; j < host_groups[i].size(); j++) { + // ATSConsistentHash needs the raw pointer. + PLHostRecord *p = host_groups[i][j].get(); + // need to copy the 'hash_string' or 'hostname' cstring to 'name' for insertion into ATSConsistentHash. + if (!p->hash_string.empty()) { + p->name = const_cast(p->hash_string.c_str()); + } else { + p->name = const_cast(p->hostname.c_str()); + } + p->group_index = host_groups[i][j]->group_index; + p->host_index = host_groups[i][j]->host_index; + hash_ring->insert(p, p->weight, &hash); + PL_NH_Debug(PL_NH_DEBUG_TAG, "Loading hash rings - ring: %d, host record: %d, name: %s, hostname: %s, stategy: %s", i, j, + p->name, p->hostname.c_str(), strategy_name.c_str()); + } + hash.clear(); + rings.push_back(hash_ring); + } + return true; +} + +// returns a hash key calculated from the request and 'hash_key' configuration +// parameter. +uint64_t +PLNextHopConsistentHash::getHashKey(uint64_t sm_id, TSMBuffer reqp, TSMLoc url, TSMLoc parent_selection_url, ATSHash64 *h) +{ + int len = 0; + const char *url_string_ref = nullptr; + + // calculate a hash using the selected config. + switch (hash_key) { + case PL_NH_URL_HASH_KEY: + url_string_ref = TSUrlStringGet(reqp, url, &len); + if (url_string_ref && len > 0) { + h->update(url_string_ref, len); + PL_NH_Debug(PL_NH_DEBUG_TAG, "[%" PRIu64 "] url hash string: %s", sm_id, url_string_ref); + } + break; + // hostname hash + case PL_NH_HOSTNAME_HASH_KEY: + url_string_ref = TSUrlHostGet(reqp, url, &len); + if (url_string_ref && len > 0) { + h->update(url_string_ref, len); + } + break; + // path + query string + case PL_NH_PATH_QUERY_HASH_KEY: + url_string_ref = TSUrlPathGet(reqp, url, &len); + h->update("/", 1); + if (url_string_ref && len > 0) { + h->update(url_string_ref, len); + } + url_string_ref = TSUrlHttpQueryGet(reqp, url, &len); + if (url_string_ref && len > 0) { + h->update("?", 1); + h->update(url_string_ref, len); + } + break; + // path + fragment hash + case PL_NH_PATH_FRAGMENT_HASH_KEY: + url_string_ref = TSUrlPathGet(reqp, url, &len); + h->update("/", 1); + if (url_string_ref && len > 0) { + h->update(url_string_ref, len); + } + url_string_ref = TSUrlHttpFragmentGet(reqp, url, &len); + if (url_string_ref && len > 0) { + h->update("?", 1); + h->update(url_string_ref, len); + } + break; + // use the cache key created by the cache-key plugin. + case PL_NH_CACHE_HASH_KEY: + if (parent_selection_url != TS_NULL_MLOC) { + url_string_ref = TSUrlStringGet(reqp, parent_selection_url, &len); + if (url_string_ref && len > 0) { + PL_NH_Debug(PL_NH_DEBUG_TAG, "[%" PRIu64 "] using parent selection over-ride string:'%.*s'.", sm_id, len, url_string_ref); + h->update(url_string_ref, len); + } + } else { + url_string_ref = TSUrlPathGet(reqp, url, &len); + h->update("/", 1); + if (url_string_ref && len > 0) { + PL_NH_Debug(PL_NH_DEBUG_TAG, "[%" PRIu64 "] the parent selection over-ride url is not set, using default path: %s.", sm_id, + url_string_ref); + h->update(url_string_ref, len); + } + } + break; + // use the path as the hash, default. + case PL_NH_PATH_HASH_KEY: + default: + url_string_ref = TSUrlPathGet(reqp, url, &len); + h->update("/", 1); + if (url_string_ref && len > 0) { + h->update(url_string_ref, len); + } + break; + } + + h->final(); + return h->get(); +} + +static void +makeNextParentErr(const char **hostname, size_t *hostname_len, in_port_t *port) +{ + *hostname = nullptr; + *hostname_len = 0; + *port = 0; +} + +void * +PLNextHopConsistentHash::newTxn() +{ + return new PLNextHopConsistentHashTxn(); +} + +void +PLNextHopConsistentHash::deleteTxn(void *txn) +{ + delete static_cast(txn); +} + +// next returns the next parent, excluding exclue_hostname:exclude_port as if it were marked down. +// +// exclude_hostname and exclude_port are parents to not consider for the next parent. +// This exists to allow getting the next parent if a failure occurs, before it occurs, +// without actually marking it down which would cause other concurrent transactions to use the wrong parent. +// This is necessary, because there's no plugin hook after a response which is a connection failure. Hacky, but it works. +// If they aren't needed, pass nullptr for exclude_hostname. +// +// out_retry is whether the returned parent was marked down, and is now being retried. +// +void +PLNextHopConsistentHash::next(TSHttpTxn txnp, void *strategyTxn, const char *exclude_hostname, size_t exclude_hostname_len, + in_port_t exclude_port, const char **out_hostname, size_t *out_hostname_len, in_port_t *out_port, + bool *out_retry, time_t now) +{ + // TODO add logic in the strategy to track when someone is retrying, and not give it out to multiple threads at once, to prevent + // thundering retries See github issue #7485 + + PL_NH_Debug(PL_NH_DEBUG_TAG, "nextParent NH plugin calling"); + + auto state = static_cast(strategyTxn); + + int64_t sm_id = TSHttpTxnIdGet(txnp); + + TSMBuffer reqp; // TODO verify doesn't need freed + + TSMLoc hdr; + ScopedFreeMLoc hdr_cleanup(&reqp, TS_NULL_MLOC, &hdr); + if (TSHttpTxnClientReqGet(txnp, &reqp, &hdr) == TS_ERROR) { + makeNextParentErr(out_hostname, out_hostname_len, out_port); + return; + } + + TSMLoc parent_selection_url = TS_NULL_MLOC; + ScopedFreeMLoc parent_selection_url_cleanup(&reqp, TS_NULL_MLOC, &parent_selection_url); + if (TSUrlCreate(reqp, &parent_selection_url) != TS_SUCCESS) { + PL_NH_Error("nexthop failed to create url for parent_selection_url"); + makeNextParentErr(out_hostname, out_hostname_len, out_port); + return; + } + if (TSHttpTxnParentSelectionUrlGet(txnp, reqp, parent_selection_url) != TS_SUCCESS) { + parent_selection_url = TS_NULL_MLOC; + } + + TSMLoc url; + ScopedFreeMLoc url_cleanup(&reqp, hdr, &url); + if (TSHttpHdrUrlGet(reqp, hdr, &url) != TS_SUCCESS) { + PL_NH_Error("failed to get header url, cannot find next hop"); + makeNextParentErr(out_hostname, out_hostname_len, out_port); + return; + } + + // TODO is it really worth getting the string out to debug print here? + PL_NH_Debug(PL_NH_DEBUG_TAG, "nextParent NH plugin findNextHop got url 'x'"); + + int64_t retry_time = 0; // = sm->t_state.txn_conf->parent_retry_time; + if (TSHttpTxnConfigIntGet(txnp, TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME, &retry_time) != TS_SUCCESS) { + // TODO get and cache on init, to prevent potential runtime failure? + PL_NH_Error("failed to get parent retry time, cannot find next hop"); + makeNextParentErr(out_hostname, out_hostname_len, out_port); + return; + } + + time_t _now = now; + bool firstcall = false; + bool nextHopRetry = false; + bool wrapped = false; + std::vector wrap_around(groups, false); + uint32_t cur_ring = 0; // there is a hash ring for each host group + uint64_t hash_key = 0; + uint32_t lookups = 0; + ATSHash64Sip24 hash; + PLHostRecord *hostRec = nullptr; + std::shared_ptr pRec = nullptr; + TSHostStatus host_stat = TSHostStatus::TS_HOST_STATUS_INIT; + + if (state->line_number == -1 && state->result == PL_NH_PARENT_UNDEFINED) { + firstcall = true; + } + + if (firstcall) { + PL_NH_Debug(PL_NH_DEBUG_TAG, "nextParent [%" PRIu64 "] firstcall, line_number: %d, result: %s", sm_id, state->line_number, + PLNHParentResultStr[state->result]); + state->line_number = PLNextHopConsistentHash::LineNumberPlaceholder; + cur_ring = 0; + for (uint32_t i = 0; i < groups; i++) { + state->chash_init[i] = false; + wrap_around[i] = false; + } + } else { + PL_NH_Debug(PL_NH_DEBUG_TAG, "getNextHopResult [%" PRIu64 "] not firstcall, line_number: %d, result: %s", sm_id, + state->line_number, PLNHParentResultStr[state->result]); + switch (ring_mode) { + case PL_NH_ALTERNATE_RING: + if (groups > 1) { + cur_ring = (state->last_group + 1) % groups; + } else { + cur_ring = state->last_group; + } + break; + case PL_NH_EXHAUST_RING: + default: + if (!wrapped) { + cur_ring = state->last_group; + } else if (groups > 1) { + cur_ring = (state->last_group + 1) % groups; + } + break; + } + } + + // Do the initial parent look-up. + hash_key = getHashKey(sm_id, reqp, url, parent_selection_url, &hash); + + do { // search until we've selected a different parent if !firstcall + std::shared_ptr r = rings[cur_ring]; + hostRec = chash_lookup(r, hash_key, &state->chashIter[cur_ring], &wrapped, &hash, &state->chash_init[cur_ring], + &state->mapWrapped[cur_ring], sm_id); + wrap_around[cur_ring] = wrapped; + lookups++; + // the 'available' flag is maintained in 'host_groups' and not the hash ring. + if (hostRec) { + pRec = host_groups[hostRec->group_index][hostRec->host_index]; + if (firstcall) { + TSHostStatus hostStatus; + const bool hostExists = + pRec ? (TSHostStatusGet(pRec->hostname.c_str(), pRec->hostname.size(), &hostStatus, nullptr) == TS_SUCCESS) : false; + state->first_choice_status = hostExists ? hostStatus : TSHostStatus::TS_HOST_STATUS_UP; + break; + } + } else { + pRec = nullptr; + } + } while (pRec && state->hostname && strncmp(pRec->hostname.c_str(), state->hostname, pRec->hostname.size()) == 0); + + PL_NH_Debug(PL_NH_DEBUG_TAG, "getNextHopResult [%" PRIu64 "] Initial parent lookups: %d", sm_id, lookups); + + // ---------------------------------------------------------------------------------------------------- + // Validate initial parent look-up and perform additional look-ups if required. + // ---------------------------------------------------------------------------------------------------- + + TSHostStatus hostStatus; + unsigned int hostReasons; + const bool hostExists = + pRec ? (TSHostStatusGet(pRec->hostname.c_str(), pRec->hostname.size(), &hostStatus, &hostReasons) == TS_SUCCESS) : false; + host_stat = hostExists ? hostStatus : TSHostStatus::TS_HOST_STATUS_UP; + // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason + // ignore the down status and mark it as avaialble + if ((pRec && ignore_self_detect) && (hostExists && hostStatus == TS_HOST_STATUS_DOWN)) { + if (hostReasons & TS_HOST_STATUS_SELF_DETECT) { + host_stat = TS_HOST_STATUS_UP; + } + } + bool pRecExclude = exclude_hostname != nullptr && strncmp(pRec->hostname.c_str(), exclude_hostname, pRec->hostname.size()) == 0 && + pRec->getPort(scheme) == exclude_port; + if (!pRec || (pRec && (!pRec->available || pRecExclude)) || host_stat == TS_HOST_STATUS_DOWN) { + do { + // check if an unavailable server is now retryable, use it if it is. + if (pRec && !pRec->available && host_stat == TS_HOST_STATUS_UP && !pRecExclude) { + _now == 0 ? _now = time(nullptr) : _now = now; + // check if the host is retryable. It's retryable if the retry window has elapsed + if ((pRec->failedAt + retry_time) < static_cast(_now)) { + nextHopRetry = true; + state->last_parent = pRec->host_index; + state->last_lookup = pRec->group_index; + state->retry = nextHopRetry; + state->result = PL_NH_PARENT_SPECIFIED; + + PL_NH_Debug(PL_NH_DEBUG_TAG, "getNextHopResult [%" PRIu64 "] next hop %.*s is now retryable, marked it available.", sm_id, + int(pRec->hostname.size()), pRec->hostname.c_str()); + break; + } + } + switch (ring_mode) { + case PL_NH_ALTERNATE_RING: + if (groups > 0) { + cur_ring = (pRec->group_index + 1) % groups; + } + break; + case PL_NH_EXHAUST_RING: + default: + if (wrap_around[cur_ring] && groups > 1) { + cur_ring = (cur_ring + 1) % groups; + } + break; + } + std::shared_ptr r = rings[cur_ring]; + hostRec = chash_lookup(r, hash_key, &state->chashIter[cur_ring], &wrapped, &hash, &state->chash_init[cur_ring], + &state->mapWrapped[cur_ring], sm_id); + wrap_around[cur_ring] = wrapped; + lookups++; + if (hostRec) { + pRec = host_groups[hostRec->group_index][hostRec->host_index]; + pRecExclude = exclude_hostname != nullptr && + strncmp(pRec->hostname.c_str(), exclude_hostname, pRec->hostname.size()) == 0 && + pRec->getPort(scheme) == exclude_port; + + TSHostStatus hostStatus; + unsigned int hostReasons; + const bool hostExists = + pRec ? (TSHostStatusGet(pRec->hostname.c_str(), pRec->hostname.size(), &hostStatus, &hostReasons) == TS_SUCCESS) : false; + host_stat = hostExists ? hostStatus : TSHostStatus::TS_HOST_STATUS_UP; + + // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason + // ignore the down status and mark it as avaialble + if ((pRec && ignore_self_detect) && (hostExists && hostStatus == TS_HOST_STATUS_DOWN)) { + if (hostReasons & TS_HOST_STATUS_SELF_DETECT) { + host_stat = TS_HOST_STATUS_UP; + } + } + PL_NH_Debug(PL_NH_DEBUG_TAG, + "nextParent [%" PRIu64 "] Selected a new parent: %.*s, available: %s, wrapped: %s, lookups: %d, exclude: %s.", + sm_id, int(pRec->hostname.size()), pRec->hostname.c_str(), (pRec->available) ? "true" : "false", + (wrapped) ? "true" : "false", lookups, pRecExclude ? "true" : "false"); + // use available host. + if (pRec->available && !pRecExclude && host_stat == TS_HOST_STATUS_UP) { + break; + } + } else { + pRec = nullptr; + } + bool all_wrapped = true; + for (uint32_t c = 0; c < groups; c++) { + if (wrap_around[c] == false) { + all_wrapped = false; + } + } + if (all_wrapped) { + PL_NH_Debug(PL_NH_DEBUG_TAG, "nextParent [%" PRIu64 "] No available parents.", sm_id); + if (pRec) { + pRec = nullptr; + } + break; + } + } while (!pRec || (pRec && (!pRec->available || pRecExclude)) || host_stat == TS_HOST_STATUS_DOWN); + } + + // ---------------------------------------------------------------------------------------------------- + // Validate and return the final result. + // ---------------------------------------------------------------------------------------------------- + + if (pRec && host_stat == TS_HOST_STATUS_UP && (pRec->available || state->retry) && !pRecExclude) { + state->result = PL_NH_PARENT_SPECIFIED; + state->hostname = pRec->hostname.c_str(); + state->hostname_len = pRec->hostname.size(); + state->last_parent = pRec->host_index; + state->last_lookup = state->last_group = cur_ring; + switch (scheme) { + case PL_NH_SCHEME_NONE: + case PL_NH_SCHEME_HTTP: + state->port = pRec->getPort(scheme); + break; + case PL_NH_SCHEME_HTTPS: + state->port = pRec->getPort(scheme); + break; + } + state->retry = nextHopRetry; + ink_assert(state->hostname != nullptr); + ink_assert(state->port != 0); + PL_NH_Debug(PL_NH_DEBUG_TAG, "nextParent [%" PRIu64 "] state->result: %s Chosen parent: %.*s:%d", sm_id, + PLNHParentResultStr[state->result], int(state->hostname_len), state->hostname, state->port); + } else { + if (go_direct == true) { + state->result = PL_NH_PARENT_DIRECT; + } else { + state->result = PL_NH_PARENT_FAIL; + } + state->hostname = nullptr; + state->hostname_len = 0; + state->port = 0; + state->retry = false; + PL_NH_Debug(PL_NH_DEBUG_TAG, "nextParent [%" PRIu64 "] state->result: %s set hostname null port 0 retry false", sm_id, + PLNHParentResultStr[state->result]); + } + + *out_hostname = state->hostname; + *out_hostname_len = state->hostname_len; + *out_port = state->port; +} + +void +PLNextHopConsistentHash::mark(TSHttpTxn txnp, void *strategyTxn, const char *hostname, const size_t hostname_len, + const in_port_t port, const PLNHCmd status, const time_t now) +{ + PL_NH_Debug(PL_NH_DEBUG_TAG, "mark calling"); + auto state = static_cast(strategyTxn); + PLStatusTxn statusTxn; + chTxnToStatusTxn(state, &statusTxn); + return passive_health.mark(txnp, &statusTxn, hostname, hostname_len, port, status, now); +} diff --git a/plugins/experimental/parent_select/consistenthash.h b/plugins/experimental/parent_select/consistenthash.h new file mode 100644 index 00000000000..7625b198c40 --- /dev/null +++ b/plugins/experimental/parent_select/consistenthash.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#pragma once + +#include "strategy.h" +#include "healthstatus.h" + +class PLNextHopSelectionStrategy; + +constexpr const unsigned int PL_NH_MAX_GROUP_RINGS = 5; + +enum PLNHHashKeyType { + PL_NH_URL_HASH_KEY = 0, + PL_NH_HOSTNAME_HASH_KEY, + PL_NH_PATH_HASH_KEY, // default, consistent hash uses the request url path + PL_NH_PATH_QUERY_HASH_KEY, + PL_NH_PATH_FRAGMENT_HASH_KEY, + PL_NH_CACHE_HASH_KEY +}; + +// The transaction state needed by PLNextHopConsistentHash about the last parent found, +// which is needed to find the next parent. +// Does not include the data already in ResponseAction. +// +// TODO initialize? zero? -1? +struct PLNextHopConsistentHashTxn { + PLNHParentResultType result = PL_NH_PARENT_UNDEFINED; + bool chash_init[PL_NH_MAX_GROUP_RINGS] = {false}; + TSHostStatus first_choice_status = TSHostStatus::TS_HOST_STATUS_INIT; + int line_number = -1; + uint32_t last_parent; + uint32_t start_parent; + uint32_t last_group; + bool wrap_around; + bool mapWrapped[2]; + int last_lookup; + ATSConsistentHashIter chashIter[PL_NH_MAX_GROUP_RINGS]; + const char *hostname = ""; + size_t hostname_len = 0; + in_port_t port = 0; + bool retry = false; +}; + +class PLNextHopConsistentHash : public PLNextHopSelectionStrategy +{ + std::vector> rings; + uint64_t getHashKey(uint64_t sm_id, TSMBuffer reqp, TSMLoc url, TSMLoc parent_selection_url, ATSHash64 *h); + +public: + const uint32_t LineNumberPlaceholder = 99999; + + PLNHHashKeyType hash_key = PL_NH_PATH_HASH_KEY; + + PLNextHopConsistentHash() = delete; + PLNextHopConsistentHash(const std::string_view name); + ~PLNextHopConsistentHash(); + bool Init(const YAML::Node &n); + void next(TSHttpTxn txnp, void *strategyTxn, const char *exclude_hostname, size_t exclude_hostname_len, in_port_t exclude_port, + const char **out_hostname, size_t *out_hostname_len, in_port_t *out_port, bool *out_retry, time_t now = 0) override; + void mark(TSHttpTxn txnp, void *strategyTxn, const char *hostname, const size_t hostname_len, const in_port_t port, + const PLNHCmd status, const time_t now) override; + void *newTxn() override; + void deleteTxn(void *txn) override; +}; diff --git a/plugins/experimental/parent_select/consistenthash_config.cc b/plugins/experimental/parent_select/consistenthash_config.cc new file mode 100644 index 00000000000..77351efaebe --- /dev/null +++ b/plugins/experimental/parent_select/consistenthash_config.cc @@ -0,0 +1,232 @@ +/* + * 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 "strategy.h" +#include "consistenthash.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "tscore/HashSip.h" +#include "tscore/ConsistentHash.h" +#include "tscore/ink_assert.h" +#include "ts/ts.h" +#include "ts/remap.h" +#include "ts/parentselectdefs.h" + +#include "consistenthash_config.h" + +void loadConfigFile(const std::string fileName, std::stringstream &doc, std::unordered_set &include_once); + +// createStrategy creates and initializes a Consistent Hash strategy from the given YAML node. +// Caller takes ownership of the returned pointer, and must call delete on it. +TSNextHopSelectionStrategy * +createStrategy(const std::string &name, const YAML::Node &node) +{ + TSDebug(PLUGIN_NAME, "createStrategy %s calling.", name.c_str()); + PLNextHopConsistentHash *st = new PLNextHopConsistentHash(name); + TSDebug(PLUGIN_NAME, "createStrategy %s newed %p.", name.c_str(), (void *)st); + if (!st->Init(node)) { + TSDebug(PLUGIN_NAME, "createStrategy %s init failed, returning nullptr.", name.c_str()); + return nullptr; + } + TSDebug(PLUGIN_NAME, "createStrategy %s init succeeded, returning obj.", name.c_str()); + return st; +} + +// Caller takes ownership of the returned pointers in the map, and must call delete on them. +// TODO change to only call createStrategy for the one we need, for efficiency. +strategies_map +createStrategiesFromFile(const char *file) +{ + TSDebug(PLUGIN_NAME, "createStrategiesFromFile plugin createStrategiesFromFile file '%s'", file); + + // return strategies_map(); // debug + + YAML::Node config; + YAML::Node strategies; + std::stringstream doc; + std::unordered_set include_once; + std::string_view fn = file; + + // strategy policy + constexpr std::string_view consistent_hash = "consistent_hash"; + + const char *basename = fn.substr(fn.find_last_of('/') + 1).data(); + + try { + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s loading ...", basename); + loadConfigFile(fn.data(), doc, include_once); + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s loaded.", basename); + + config = YAML::Load(doc); + + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s yaml loaded.", basename); + + if (config.IsNull()) { + TSDebug(PLUGIN_NAME, "createStrategiesFromFile No NextHop strategy configs were loaded."); + return strategies_map(); + } + + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s checked null.", basename); + + strategies = config["strategies"]; + if (strategies.Type() != YAML::NodeType::Sequence) { + TSError("[%s] malformed %s file, expected a 'strategies' sequence", PLUGIN_NAME, basename); + return strategies_map(); + } + + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s checked strategies member.", basename); + + // std::map> + strategies_map strategiesMap; + for (unsigned int i = 0; i < strategies.size(); ++i) { + YAML::Node strategy = strategies[i]; + auto name = strategy["strategy"].as(); + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s got strategy %s.", basename, name.c_str()); + auto policy = strategy["policy"]; + if (!policy) { + TSError("[%s] no policy is defined for the strategy named '%s'.", PLUGIN_NAME, name.c_str()); + return strategies_map(); + } + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s got strategy %s checked policy.", basename, name.c_str()); + auto policy_value = policy.Scalar(); + if (policy_value != consistent_hash) { + TSError("[%s] strategy named '%s' has unsupported policy '%s'.", PLUGIN_NAME, name.c_str(), policy_value.c_str()); + return strategies_map(); + } + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s got strategy %s creating strategy.", basename, name.c_str()); + TSNextHopSelectionStrategy *tsStrategy = createStrategy(name, strategy); + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s got strategy %s created strategy.", basename, name.c_str()); + if (tsStrategy == nullptr) { + return strategies_map(); + } + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s got strategy %s checked strategy null.", basename, name.c_str()); + strategiesMap.emplace(name, std::unique_ptr(tsStrategy)); + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s got strategy %s emplaced.", basename, name.c_str()); + } + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s returning strategies created.", basename); + return strategiesMap; + } catch (std::exception &ex) { + TSError("[%s] creating strategies from file %s threw '%s'.", PLUGIN_NAME, file, ex.what()); + } + TSDebug(PLUGIN_NAME, "createStrategiesFromFile filename %s returning error.", basename); + return strategies_map(); +} + +/* + * loads the contents of a file into a std::stringstream document. If the file has a '#include file' + * directive, that 'file' is read into the document beginning at the the point where the + * '#include' was found. This allows the 'strategy' and 'hosts' yaml files to be separate. The + * 'strategy' yaml file would then normally have the '#include hosts.yml' in it's begining. + */ +void +loadConfigFile(const std::string fileName, std::stringstream &doc, std::unordered_set &include_once) +{ + const char *sep = " \t"; + char *tok, *last; + struct stat buf; + std::string line; + + if (stat(fileName.c_str(), &buf) == -1) { + std::string err_msg = strerror(errno); + throw std::invalid_argument("Unable to stat '" + fileName + "': " + err_msg); + } + + // if fileName is a directory, concatenate all '.yaml' files alphanumerically + // into a single document stream. No #include is supported. + if (S_ISDIR(buf.st_mode)) { + DIR *dir = nullptr; + struct dirent *dir_ent = nullptr; + std::vector files; + + TSDebug(PLUGIN_NAME, "loading strategy YAML files from the directory %s", fileName.c_str()); + if ((dir = opendir(fileName.c_str())) == nullptr) { + std::string err_msg = strerror(errno); + throw std::invalid_argument("Unable to open the directory '" + fileName + "': " + err_msg); + } else { + while ((dir_ent = readdir(dir)) != nullptr) { + // filename should be greater that 6 characters to have a '.yaml' suffix. + if (strlen(dir_ent->d_name) < 6) { + continue; + } + std::string_view sv = dir_ent->d_name; + if (sv.find(".yaml", sv.size() - 5) == sv.size() - 5) { + files.push_back(sv); + } + } + // sort the files alphanumerically + std::sort(files.begin(), files.end(), + [](const std::string_view lhs, const std::string_view rhs) { return lhs.compare(rhs) < 0; }); + + for (uint32_t i = 0; i < files.size(); i++) { + std::ifstream file(fileName + "/" + files[i].data()); + if (file.is_open()) { + while (std::getline(file, line)) { + if (line[0] == '#') { + // continue; + } + doc << line << "\n"; + } + file.close(); + } else { + throw std::invalid_argument("Unable to open and read '" + fileName + "/" + files[i].data() + "'"); + } + } + } + } else { + std::ifstream file(fileName); + if (file.is_open()) { + while (std::getline(file, line)) { + if (line[0] == '#') { + tok = strtok_r(const_cast(line.c_str()), sep, &last); + if (tok != nullptr && strcmp(tok, "#include") == 0) { + std::string f = strtok_r(nullptr, sep, &last); + if (include_once.find(f) == include_once.end()) { + include_once.insert(f); + // try to load included file. + try { + loadConfigFile(f, doc, include_once); + } catch (std::exception &ex) { + throw std::invalid_argument("Unable to open included file '" + f + "' from '" + fileName + "'"); + } + } + } + } else { + doc << line << "\n"; + } + } + file.close(); + } else { + throw std::invalid_argument("Unable to open and read '" + fileName + "'"); + } + } +} diff --git a/plugins/experimental/parent_select/consistenthash_config.h b/plugins/experimental/parent_select/consistenthash_config.h new file mode 100644 index 00000000000..3d26e83eb02 --- /dev/null +++ b/plugins/experimental/parent_select/consistenthash_config.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include "ts/parentselectdefs.h" + +class TSNextHopSelectionStrategy; + +typedef std::map, std::less<>> strategies_map; + +strategies_map createStrategiesFromFile(const char *file); +TSNextHopSelectionStrategy *createStrategy(const std::string &name, const YAML::Node &node); diff --git a/plugins/experimental/parent_select/healthstatus.cc b/plugins/experimental/parent_select/healthstatus.cc new file mode 100644 index 00000000000..864927ba088 --- /dev/null +++ b/plugins/experimental/parent_select/healthstatus.cc @@ -0,0 +1,147 @@ +/* + * 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 "strategy.h" +#include "consistenthash.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "tscore/HashSip.h" +#include "tscore/ConsistentHash.h" +#include "tscore/ink_assert.h" +#include "ts/ts.h" +#include "ts/remap.h" +#include "ts/parentselectdefs.h" + +void +PLNextHopHealthStatus::insert(std::vector> &hosts) +{ + for (uint32_t ii = 0; ii < hosts.size(); ii++) { + std::shared_ptr h = hosts[ii]; + for (auto protocol = h->protocols.begin(); protocol != h->protocols.end(); ++protocol) { + const std::string host_port = h->getHostPort((*protocol)->port); + host_map.emplace(std::make_pair(host_port, h)); + PL_NH_Debug(PL_NH_DEBUG_TAG, "inserting %s into host_map", host_port.c_str()); + } + } +} + +void +PLNextHopHealthStatus::mark(TSHttpTxn txnp, PLStatusTxn *state, const char *hostname, const size_t hostname_len, + const in_port_t port, const PLNHCmd status, const time_t now) +{ + const time_t _now = now == 0 ? time(nullptr) : now; + + const int64_t sm_id = TSHttpTxnIdGet(txnp); + + int64_t fail_threshold; // = sm->t_state.txn_conf->parent_fail_threshold; + if (TSHttpTxnConfigIntGet(txnp, TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD, &fail_threshold) != TS_SUCCESS) { + PL_NH_Error("mark failed to get parent_fail_threshold, cannot mark next hop"); + return; + } + + int64_t retry_time; // = sm->t_state.txn_conf->parent_retry_time; + if (TSHttpTxnConfigIntGet(txnp, TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME, &retry_time) != TS_SUCCESS) { + PL_NH_Error("mark failed to get parent_retry_time, cannot mark next hop"); + return; + } + + uint32_t new_fail_count = 0; + + // make sure we're called back with a result structure for a parent + // that is being retried. + if (status == PL_NH_MARK_UP) { + ink_assert(state->retry == true); + } + if (state->result != PL_NH_PARENT_SPECIFIED) { + return; + } + + const std::string host_port = PLHostRecord::makeHostPort(std::string_view(hostname, hostname_len), port); + auto iter = host_map.find(host_port); + if (iter == host_map.end()) { + PL_NH_Debug(PL_NH_DEBUG_TAG, "[%" PRId64 "] no host named %s found in host_map", sm_id, host_port.c_str()); + return; + } + + std::shared_ptr h = iter->second; + + switch (status) { + // Mark the host up. + case PL_NH_MARK_UP: + if (!h->available) { + h->set_available(); + PL_NH_Note("[%" PRId64 "] http parent proxy %s restored", sm_id, hostname); + } + break; + // Mark the host down. + case PL_NH_MARK_DOWN: + if (h->failedAt == 0 || state->retry == true) { + { // lock guard + std::lock_guard guard(h->_mutex); + if (h->failedAt == 0) { + h->failedAt = _now; + if (state->retry == false) { + new_fail_count = h->failCount = 1; + } + } else if (state->retry == true) { + h->failedAt = _now; + } + } // end lock guard + PL_NH_Note("[%" PRId64 "] NextHop %s marked as down %s", sm_id, (state->retry) ? "retry" : "initially", h->hostname.c_str()); + } else { + int old_count = 0; + // if the last failure was outside the retry window, set the failcount to 1 and failedAt to now. + { // lock guard + std::lock_guard lock(h->_mutex); + if ((h->failedAt + retry_time) < static_cast(_now)) { + h->failCount = 1; + h->failedAt = _now; + } else { + old_count = h->failCount = 1; + } + new_fail_count = old_count + 1; + } // end of lock_guard + PL_NH_Debug(PL_NH_DEBUG_TAG, "[%" PRId64 "] Parent fail count increased to %d for %s", sm_id, new_fail_count, + h->hostname.c_str()); + } + + if (new_fail_count >= fail_threshold) { + h->set_unavailable(); + PL_NH_Note("[%" PRId64 "] Failure threshold met failcount:%d >= threshold:%" PRId64 ", http parent proxy %s marked down", + sm_id, new_fail_count, fail_threshold, h->hostname.c_str()); + PL_NH_Debug(PL_NH_DEBUG_TAG, "[%" PRId64 "] NextHop %s marked unavailable, h->available=%s", sm_id, h->hostname.c_str(), + (h->available) ? "true" : "false"); + } + break; + } +} diff --git a/plugins/experimental/parent_select/healthstatus.h b/plugins/experimental/parent_select/healthstatus.h new file mode 100644 index 00000000000..66d6295cac0 --- /dev/null +++ b/plugins/experimental/parent_select/healthstatus.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "ts/parentselectdefs.h" + +struct PLHostRecord; + +enum PLNHCmd { PL_NH_MARK_UP, PL_NH_MARK_DOWN }; + +typedef enum { + PL_NH_PARENT_UNDEFINED, + PL_NH_PARENT_DIRECT, + PL_NH_PARENT_SPECIFIED, + PL_NH_PARENT_AGENT, + PL_NH_PARENT_FAIL, +} PLNHParentResultType; + +struct PLStatusTxn { + PLNHParentResultType result = PL_NH_PARENT_UNDEFINED; + bool retry = false; +}; + +class PLNextHopHealthStatus +{ +public: + void insert(std::vector> &hosts); + void mark(TSHttpTxn txn, PLStatusTxn *healthTxn, const char *hostname, const size_t hostname_len, const in_port_t port, + const PLNHCmd status, const time_t now = 0); + PLNextHopHealthStatus(){}; + +private: + std::unordered_map> host_map; +}; diff --git a/plugins/experimental/parent_select/parent_select.cc b/plugins/experimental/parent_select/parent_select.cc new file mode 100644 index 00000000000..31519013469 --- /dev/null +++ b/plugins/experimental/parent_select/parent_select.cc @@ -0,0 +1,460 @@ +/** @file + + This plugin counts the number of times every header has appeared. + Maintains separate counts for client and origin headers. + + @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 +#include +#include +#include +#include +#include +#include + +#include "ts/ts.h" +#include "ts/remap.h" +#include "ts/parentselectdefs.h" + +#include "consistenthash_config.h" +#include "strategy.h" + +// TODO summary: +// - TSRemapInit version check + +namespace +{ +// The strategy and its transaction state. +struct StrategyTxn { + TSNextHopSelectionStrategy *strategy; + void *txn; // void* because the actual type will depend on the strategy. + int request_count; + const char *prev_host; // the actually tried host, used when send_request sets the response_action to be the next thing to try. + size_t prev_host_len; + in_port_t prev_port; + bool prev_is_retry; +}; + +int +handle_send_request(TSHttpTxn txnp, StrategyTxn *strategyTxn) +{ + TSDebug(PLUGIN_NAME, "handle_send_request calling"); + TSDebug(PLUGIN_NAME, "handle_send_request got strategy '%s'", strategyTxn->strategy->name()); + + auto strategy = strategyTxn->strategy; + + // if (strategyTxn->retry_attempts == 0) { + // // just did a DoRemap, which means we need to set the response_action of what to do in the event of failure + // // because a failure might not call read_response (e.g. dns failure) + // strategyTxn->retry_attempts = 1; + // TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + // return TS_SUCCESS; + // } + + // before sending a req, we need to set what to do on failure. + // Because some failures don't call handle_response before getting to HttpTransact::HandleResponse + // (e.g. connection failures) + + TSResponseAction ra; + TSHttpTxnResponseActionGet(txnp, &ra); + + TSDebug(PLUGIN_NAME, "handle_send_request setting prev %.*s:%d", int(ra.hostname_len), ra.hostname, ra.port); + strategyTxn->prev_host = ra.hostname; + strategyTxn->prev_host_len = ra.hostname_len; + strategyTxn->prev_port = ra.port; + strategyTxn->prev_is_retry = ra.is_retry; + + strategy->next(txnp, strategyTxn->txn, ra.hostname, ra.hostname_len, ra.port, &ra.hostname, &ra.hostname_len, &ra.port, + &ra.is_retry); + + ra.nextHopExists = strategy->nextHopExists(txnp); + ra.fail = !ra.nextHopExists; // failed is whether to fail and return to the client. failed=false means to retry the parent we set + // in the response_action + + // we don't know if it's retryable yet, because we don't have a status. So set it retryable if we have something which could be + // retried. We'll set it retryable per the status in handle_response, and os_dns (which is called on connection failures, and + // always retryable [notwithstanding num_retries]). + ra.responseIsRetryable = ra.nextHopExists; + ra.goDirect = strategy->goDirect(); + ra.parentIsProxy = strategy->parentIsProxy(); + + TSDebug(PLUGIN_NAME, "handle_send_request setting response_action hostname '%.*s' port %d direct %d proxy %d", + int(ra.hostname_len), ra.hostname, ra.port, ra.goDirect, ra.parentIsProxy); + TSHttpTxnResponseActionSet(txnp, &ra); + + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return TS_SUCCESS; +} + +// mark parents up or down, on failure or successful retry. +void +mark_response(TSHttpTxn txnp, StrategyTxn *strategyTxn, TSHttpStatus status) +{ + TSDebug(PLUGIN_NAME, "mark_response calling with code: %d", status); + + auto strategy = strategyTxn->strategy; + + const bool isFailure = strategy->codeIsFailure(status); + + TSResponseAction ra; + // if the prev_host isn't null, then that was the actual host we tried which needs to be marked down. + if (strategyTxn->prev_host != nullptr) { + ra.hostname = strategyTxn->prev_host; + ra.hostname_len = strategyTxn->prev_host_len; + ra.port = strategyTxn->prev_port; + ra.is_retry = strategyTxn->prev_is_retry; + TSDebug(PLUGIN_NAME, "mark_response using prev %.*s:%d", int(ra.hostname_len), ra.hostname, ra.port); + } else { + TSHttpTxnResponseActionGet(txnp, &ra); + TSDebug(PLUGIN_NAME, "mark_response using response_action %.*s:%d", int(ra.hostname_len), ra.hostname, ra.port); + } + if (isFailure && strategy->onFailureMarkParentDown(status)) { + if (ra.hostname == nullptr) { + TSError( + "[%s] mark_response got a failure, but response_action had no hostname! This shouldn't be possible! Not marking down!", + PLUGIN_NAME); + } else { + TSDebug(PLUGIN_NAME, "mark_response marking %.*s:%d down", int(ra.hostname_len), ra.hostname, ra.port); + strategy->mark(txnp, strategyTxn->txn, ra.hostname, ra.hostname_len, ra.port, PL_NH_MARK_DOWN); + } + } else if (!isFailure && ra.is_retry) { + if (ra.hostname == nullptr) { + TSError( + "[%s] mark_response got a retry success, but response_action had no hostname! This shouldn't be possible! Not marking up!", + PLUGIN_NAME); + } else { + TSDebug(PLUGIN_NAME, "mark_response marking %.*s:%d up", int(ra.hostname_len), ra.hostname, ra.port); + strategy->mark(txnp, strategyTxn->txn, ra.hostname, ra.hostname_len, ra.port, PL_NH_MARK_UP); + } + } +} + +int +handle_read_response(TSHttpTxn txnp, StrategyTxn *strategyTxn) +{ + TSDebug(PLUGIN_NAME, "handle_read_response calling"); + + auto strategy = strategyTxn->strategy; + + TSDebug(PLUGIN_NAME, "handle_read_response got strategy '%s'", strategy->name()); + + // increment request count here, not send_request, because we need to consistently increase with os_dns hooks. + // if we incremented the req count in send_request and not here, that would never be called on DNS failures, but DNS successes + // would call os_dns and also send_request, resulting in dns failures incrementing the count by 1, and dns successes but http + // failures would increment by 2. And successes would increment by 2. Hence, the only consistent way to count requests is on + // read_response and os_dns, and not send_request. + ++strategyTxn->request_count; + + TSMBuffer resp; + TSMLoc resp_hdr; + if (TS_SUCCESS != TSHttpTxnServerRespGet(txnp, &resp, &resp_hdr)) { + TSDebug(PLUGIN_NAME, "handle_read_response failed to get resp"); + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return TS_SUCCESS; + } + + TSHttpStatus status = TSHttpHdrStatusGet(resp, resp_hdr); + TSDebug(PLUGIN_NAME, "handle_read_response got response code: %d", status); + + mark_response(txnp, strategyTxn, status); + + if (!strategy->codeIsFailure(status)) { + // if it's a success, set the action to not retry + TSResponseAction ra; + // this sets failed=false, responseIsRetryable=false => don't retry, return the success + memset(&ra, 0, sizeof(TSResponseAction)); // because {0} gives a C++ warning. Ugh. + TSDebug(PLUGIN_NAME, "handle_read_response success, setting response_action to not retry"); + TSHttpTxnResponseActionSet(txnp, &ra); + } else { + // We already set the response_action for what to do on failure in send_request. + // (because we don't always get here with responses, like DNS or connection failures) + // But we need to get the action previously set, and update responseIsRetryable, which we couldn't determine before without the + // Status. + TSResponseAction ra; + TSHttpTxnResponseActionGet(txnp, &ra); + ra.responseIsRetryable = strategy->responseIsRetryable(strategyTxn->request_count, status); + TSHttpTxnResponseActionSet(txnp, &ra); + } + + // un-set the "prev" hackery. That only exists for markdown, which we just did. + // The response_action is now the next thing to try, if this was a failure, + // and should now be considered authoritative for everything. + strategyTxn->prev_host = nullptr; + strategyTxn->prev_host_len = 0; + strategyTxn->prev_port = 0; + strategyTxn->prev_is_retry = false; + + TSHandleMLocRelease(resp, TS_NULL_MLOC, resp_hdr); + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return TS_SUCCESS; +} + +int +handle_os_dns(TSHttpTxn txnp, StrategyTxn *strategyTxn) +{ + TSDebug(PLUGIN_NAME, "handle_os_dns calling"); + + ++strategyTxn->request_count; // this is called after connection failures. So if we got here, we attempted a request + + // This is not called on the first attempt. + // Thus, if we got called here, we know it's because of a parent failure. + // So immediately find the next parent, and set the response_action. + + auto strategy = strategyTxn->strategy; + + TSDebug(PLUGIN_NAME, "handle_os_dns got strategy '%s'", strategy->name()); + + mark_response(txnp, strategyTxn, STATUS_CONNECTION_FAILURE); + + // now, we need to figure out, are we the first call after send_response set the response_action as the next-thing-to-try, + // or are we a subsequent call, and need to actually set a new response_action + + if (strategyTxn->prev_host != nullptr) { + TSDebug(PLUGIN_NAME, "handle_os_dns had prev, keeping existing response_action and un-setting prev"); + // if strategyTxn->prev_host exists, this is the very first call after send_response set the response_action to the next thing + // to try. and no handle_response was called in-between (because it was a connection or dns failure) So keep that, and set + // prev_host=nullptr (so we get a new response_action the next time we're called) + strategyTxn->prev_host = nullptr; + strategyTxn->prev_port = 0; + strategyTxn->prev_is_retry = false; + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return TS_SUCCESS; + } + + TSDebug(PLUGIN_NAME, "handle_os_dns had no prev, setting new response_action"); + + TSResponseAction ra; + memset(&ra, 0, sizeof(TSResponseAction)); // because {0} gives a C++ warning. Ugh. + const char *const exclude_host = nullptr; + const size_t exclude_host_len = 0; + const in_port_t exclude_port = 0; + strategy->next(txnp, strategyTxn->txn, exclude_host, exclude_host_len, exclude_port, &ra.hostname, &ra.hostname_len, &ra.port, + &ra.is_retry); + + ra.fail = ra.hostname == nullptr; // failed is whether to immediately fail and return the client a 502. In this case: whether or + // not we found another parent. + ra.nextHopExists = strategy->nextHopExists(txnp); + ra.responseIsRetryable = strategy->responseIsRetryable(strategyTxn->request_count, STATUS_CONNECTION_FAILURE); + ra.goDirect = strategy->goDirect(); + ra.parentIsProxy = strategy->parentIsProxy(); + TSDebug(PLUGIN_NAME, "handle_os_dns setting response_action hostname '%.*s' port %d direct %d proxy %d is_retry %d exists %d", + int(ra.hostname_len), ra.hostname, ra.port, ra.goDirect, ra.parentIsProxy, ra.is_retry, ra.nextHopExists); + TSHttpTxnResponseActionSet(txnp, &ra); + + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return TS_SUCCESS; +} + +int +handle_txn_close(TSHttpTxn txnp, TSCont contp, StrategyTxn *strategyTxn) +{ + TSDebug(PLUGIN_NAME, "handle_txn_close calling"); + + auto strategy = strategyTxn->strategy; + + if (strategy != nullptr) { + TSContDataSet(contp, nullptr); + strategy->deleteTxn(strategyTxn->txn); + delete strategyTxn; + // we delete the state, and the strategyAndState pointer at the end of the transaction, + // but we DON'T delete the Strategy, which lives as long as the remap. + } + TSContDestroy(contp); + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return TS_SUCCESS; +} + +int +handle_hook(TSCont contp, TSEvent event, void *edata) +{ + TSDebug(PLUGIN_NAME, "handle_hook calling"); + + TSHttpTxn txnp = static_cast(edata); + StrategyTxn *strategyTxn = static_cast(TSContDataGet(contp)); + + TSDebug(PLUGIN_NAME, "handle_hook got strategy '%s'", strategyTxn->strategy->name()); + + switch (event) { + // case TS_EVENT_HTTP_READ_REQUEST_HDR: + // return handle_read_request(txnp, strategyTxn); + case TS_EVENT_HTTP_SEND_REQUEST_HDR: + return handle_send_request(txnp, strategyTxn); + case TS_EVENT_HTTP_READ_RESPONSE_HDR: + return handle_read_response(txnp, strategyTxn); + // case TS_EVENT_HTTP_SEND_RESPONSE_HDR: + // return handle_send_response(txnp, strategyTxn); + case TS_EVENT_HTTP_OS_DNS: + return handle_os_dns(txnp, strategyTxn); + case TS_EVENT_HTTP_TXN_CLOSE: + return handle_txn_close(txnp, contp, strategyTxn); + default: + TSError("[%s] handle_hook got unknown event %d - should never happen!", PLUGIN_NAME, event); + return TS_ERROR; + } +} + +} // namespace + +TSReturnCode +TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) +{ + TSDebug(PLUGIN_NAME, "TSRemapInit calling"); + + // TODO add ATS API Version check here, to bail if ATS doesn't support the version necessary for strategy plugins + + if (!api_info) { + strncpy(errbuf, "[tsstrategy_init] - Invalid TSRemapInterface argument", errbuf_size - 1); + return TS_ERROR; + } + + if (api_info->tsremap_version < TSREMAP_VERSION) { + snprintf(errbuf, errbuf_size, "[TSStrategyInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16, + (api_info->tsremap_version & 0xffff)); + return TS_ERROR; + } + + TSDebug(PLUGIN_NAME, "Remap successfully initialized"); + return TS_SUCCESS; +} + +TSReturnCode +TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuff, int errbuff_size) +{ + TSDebug(PLUGIN_NAME, "TSRemapNewInstance calling"); + + *ih = nullptr; + + for (int i = 0; i < argc; ++i) { + TSDebug(PLUGIN_NAME, "TSRemapNewInstance arg %d '%s'", i, argv[i]); + } + + if (argc < 4) { + TSError("[%s] insufficient number of arguments, %d, expected file and strategy argument.", PLUGIN_NAME, argc); + return TS_ERROR; + } + if (argc > 4) { + TSError("[%s] too many arguments, %d, only expected file and strategy argument.", PLUGIN_NAME, argc); + return TS_ERROR; + } + + const char *remap_from = argv[0]; + const char *remap_to = argv[1]; + const char *config_file_path = argv[2]; + const char *strategy_name = argv[3]; + + TSDebug(PLUGIN_NAME, "%s %s Loading parent selection strategy file %s for strategy %s", remap_from, remap_to, config_file_path, + strategy_name); + auto file_strategies = createStrategiesFromFile(config_file_path); + if (file_strategies.size() == 0) { + TSError("[%s] %s %s Failed to parse configuration file %s", PLUGIN_NAME, remap_from, remap_to, config_file_path); + return TS_ERROR; + } + + TSDebug(PLUGIN_NAME, "'%s' '%s' successfully created strategies in file %s num %d", remap_from, remap_to, config_file_path, + int(file_strategies.size())); + + std::unique_ptr strategy; + + for (auto it = file_strategies.begin(); it != file_strategies.end(); it++) { + TSDebug(PLUGIN_NAME, "'%s' '%s' TSRemapNewInstance strategy file had strategy named '%s'", remap_from, remap_to, + it->first.c_str()); + if (strncmp(strategy_name, it->first.c_str(), strlen(strategy_name)) != 0) { + continue; + } + TSDebug(PLUGIN_NAME, "'%s' '%s' TSRemapNewInstance using '%s'", remap_from, remap_to, it->first.c_str()); + strategy = std::move(it->second); + } + + if (strategy.get() == nullptr) { + TSDebug(PLUGIN_NAME, "'%s' '%s' TSRemapNewInstance strategy '%s' not found in file '%s'", remap_from, remap_to, strategy_name, + config_file_path); + return TS_ERROR; + } + + TSDebug(PLUGIN_NAME, "'%s' '%s' TSRemapNewInstance successfully loaded strategy '%s' from '%s'.", remap_from, remap_to, + strategy_name, config_file_path); + *ih = static_cast(strategy.release()); + return TS_SUCCESS; +} + +extern "C" tsapi TSRemapStatus +TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri) +{ + TSDebug(PLUGIN_NAME, "TSRemapDoRemap calling"); + + auto strategy = static_cast(ih); + + TSDebug(PLUGIN_NAME, "TSRemapDoRemap got strategy '%s'", strategy->name()); + + TSCont cont = TSContCreate(handle_hook, TSMutexCreate()); + + auto strategyTxn = new StrategyTxn(); + strategyTxn->strategy = strategy; + strategyTxn->txn = strategy->newTxn(); + strategyTxn->request_count = 0; + strategyTxn->prev_host = nullptr; + strategyTxn->prev_port = 0; + strategyTxn->prev_is_retry = false; + TSContDataSet(cont, (void *)strategyTxn); + + // TSHttpTxnHookAdd(txnp, TS_HTTP_READ_REQUEST_HDR_HOOK, cont); + TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, cont); + TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, cont); + // TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, cont); + TSHttpTxnHookAdd(txnp, TS_HTTP_OS_DNS_HOOK, cont); + TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, cont); + + TSResponseAction ra; + memset(&ra, 0, sizeof(TSResponseAction)); // because {0} gives a C++ warning. Ugh. + constexpr const char *const exclude_host = nullptr; + constexpr const size_t exclude_host_len = 0; + constexpr const in_port_t exclude_port = 0; + strategy->next(txnp, strategyTxn->txn, exclude_host, exclude_host_len, exclude_port, &ra.hostname, &ra.hostname_len, &ra.port, + &ra.is_retry); + + if (ra.hostname == nullptr) { + // TODO make configurable + TSDebug(PLUGIN_NAME, "TSRemapDoRemap strategy '%s' next returned nil, returning 502!", strategy->name()); + TSHttpTxnStatusSet(txnp, TS_HTTP_STATUS_BAD_GATEWAY); + // TODO verify TS_EVENT_HTTP_TXN_CLOSE fires, and if not, free the cont here. + return TSREMAP_DID_REMAP; + } + + ra.fail = false; + ra.nextHopExists = true; + ra.responseIsRetryable = + true; // The action here is used for the very first connection, not any retry. So of course we should try it. + ra.goDirect = strategy->goDirect(); + ra.parentIsProxy = strategy->parentIsProxy(); + TSDebug(PLUGIN_NAME, "TSRemapDoRemap setting response_action hostname '%.*s' port %d direct %d proxy %d", int(ra.hostname_len), + ra.hostname, ra.port, ra.goDirect, ra.parentIsProxy); + TSHttpTxnResponseActionSet(txnp, &ra); + + return TSREMAP_NO_REMAP; +} + +extern "C" tsapi void +TSRemapDeleteInstance(void *ih) +{ + TSDebug(PLUGIN_NAME, "TSRemapDeleteInstance calling"); + auto strategy = static_cast(ih); + delete strategy; +} diff --git a/plugins/experimental/parent_select/strategy.cc b/plugins/experimental/parent_select/strategy.cc new file mode 100644 index 00000000000..7fc7407b4bb --- /dev/null +++ b/plugins/experimental/parent_select/strategy.cc @@ -0,0 +1,349 @@ +/* + * 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 "strategy.h" +#include "consistenthash.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "tscore/HashSip.h" +#include "tscore/ConsistentHash.h" +#include "tscore/ink_assert.h" +#include "ts/ts.h" +#include "ts/remap.h" +#include "ts/parentselectdefs.h" + +// +// NextHopSelectionStrategy.cc +// + +// ring mode strings +constexpr std::string_view alternate_rings = "alternate_ring"; +constexpr std::string_view exhaust_rings = "exhaust_ring"; + +// health check strings +constexpr std::string_view active_health_check = "active"; +constexpr std::string_view passive_health_check = "passive"; + +PLNextHopSelectionStrategy::PLNextHopSelectionStrategy(const std::string_view &name) +{ + strategy_name = name; +} + +// +// parse out the data for this strategy. +// +bool +PLNextHopSelectionStrategy::Init(const YAML::Node &n) +{ + PL_NH_Debug(PL_NH_DEBUG_TAG, "calling Init()"); + + try { + if (n["scheme"]) { + auto scheme_val = n["scheme"].Scalar(); + if (scheme_val == "http") { + scheme = PL_NH_SCHEME_HTTP; + } else if (scheme_val == "https") { + scheme = PL_NH_SCHEME_HTTPS; + } else { + scheme = PL_NH_SCHEME_NONE; + PL_NH_Note("Invalid 'scheme' value, '%s', for the strategy named '%s', setting to PL_NH_SCHEME_NONE", scheme_val.c_str(), + strategy_name.c_str()); + } + } + + // go_direct config. + if (n["go_direct"]) { + go_direct = n["go_direct"].as(); + } + + // parent_is_proxy config. + if (n["parent_is_proxy"]) { + parent_is_proxy = n["parent_is_proxy"].as(); + } + + // ignore_self_detect + if (n["ignore_self_detect"]) { + ignore_self_detect = n["ignore_self_detect"].as(); + } + + // failover node. + YAML::Node failover_node; + if (n["failover"]) { + failover_node = n["failover"]; + if (failover_node["ring_mode"]) { + auto ring_mode_val = failover_node["ring_mode"].Scalar(); + if (ring_mode_val == alternate_rings) { + ring_mode = PL_NH_ALTERNATE_RING; + } else if (ring_mode_val == exhaust_rings) { + ring_mode = PL_NH_EXHAUST_RING; + } else { + ring_mode = PL_NH_ALTERNATE_RING; + PL_NH_Note("Invalid 'ring_mode' value, '%s', for the strategy named '%s', using default '%s'.", ring_mode_val.c_str(), + strategy_name.c_str(), alternate_rings.data()); + } + } + if (failover_node["max_simple_retries"]) { + max_simple_retries = failover_node["max_simple_retries"].as(); + } + + YAML::Node resp_codes_node; + // connection failures are always failure and retryable (pending retries) + resp_codes.add(STATUS_CONNECTION_FAILURE); + if (failover_node["response_codes"]) { + resp_codes_node = failover_node["response_codes"]; + if (resp_codes_node.Type() != YAML::NodeType::Sequence) { + PL_NH_Error("Error in the response_codes definition for the strategy named '%s', skipping response_codes.", + strategy_name.c_str()); + } else { + for (unsigned int k = 0; k < resp_codes_node.size(); ++k) { + auto code = resp_codes_node[k].as(); + if (code > 300 && code < 599) { + resp_codes.add(code); + } else { + PL_NH_Note("Skipping invalid response code '%d' for the strategy named '%s'.", code, strategy_name.c_str()); + } + } + resp_codes.sort(); + } + } + YAML::Node health_check_node; + if (failover_node["health_check"]) { + health_check_node = failover_node["health_check"]; + if (health_check_node.Type() != YAML::NodeType::Sequence) { + PL_NH_Error("Error in the health_check definition for the strategy named '%s', skipping health_checks.", + strategy_name.c_str()); + } else { + for (auto it = health_check_node.begin(); it != health_check_node.end(); ++it) { + auto health_check = it->as(); + if (health_check.compare(active_health_check) == 0) { + health_checks.active = true; + } + if (health_check.compare(passive_health_check) == 0) { + health_checks.passive = true; + } + } + } + } + } + + // parse and load the host data + YAML::Node groups_node; + if (n["groups"]) { + groups_node = n["groups"]; + // a groups list is required. + if (groups_node.Type() != YAML::NodeType::Sequence) { + throw std::invalid_argument("Invalid groups definition, expected a sequence, '" + strategy_name + "' cannot be loaded."); + } else { + uint32_t grp_size = groups_node.size(); + if (grp_size > PL_NH_MAX_GROUP_RINGS) { + PL_NH_Note( + "the groups list exceeds the maximum of %d for the strategy '%s'. Only the first %d groups will be configured.", + PL_NH_MAX_GROUP_RINGS, strategy_name.c_str(), PL_NH_MAX_GROUP_RINGS); + groups = PL_NH_MAX_GROUP_RINGS; + } else { + groups = groups_node.size(); + } + // resize the hosts vector. + host_groups.reserve(groups); + // loop through the groups + for (unsigned int grp = 0; grp < groups; ++grp) { + YAML::Node hosts_list = groups_node[grp]; + + // a list of hosts is required. + if (hosts_list.Type() != YAML::NodeType::Sequence) { + throw std::invalid_argument("Invalid hosts definition, expected a sequence, '" + strategy_name + "' cannot be loaded."); + } else { + // loop through the hosts list. + std::vector> hosts_inner; + + for (unsigned int hst = 0; hst < hosts_list.size(); ++hst) { + std::shared_ptr host_rec = std::make_shared(hosts_list[hst].as()); + host_rec->group_index = grp; + host_rec->host_index = hst; + if (TSHostnameIsSelf(host_rec->hostname.c_str(), host_rec->hostname.size()) == TS_SUCCESS) { + TSHostStatusSet(host_rec->hostname.c_str(), host_rec->hostname.size(), TSHostStatus::TS_HOST_STATUS_DOWN, 0, + (unsigned int)TS_HOST_STATUS_SELF_DETECT); + } + hosts_inner.push_back(std::move(host_rec)); + num_parents++; + } + passive_health.insert(hosts_inner); + host_groups.push_back(std::move(hosts_inner)); + } + } + } + } + } catch (std::exception &ex) { + PL_NH_Note("Error parsing the strategy named '%s' due to '%s', this strategy will be ignored.", strategy_name.c_str(), + ex.what()); + return false; + } + + return true; +} + +bool +PLNextHopSelectionStrategy::nextHopExists(TSHttpTxn txnp) +{ + PL_NH_Debug(PL_NH_DEBUG_TAG, "nhplugin nextHopExists calling"); + + const int64_t sm_id = TSHttpTxnIdGet(txnp); + + for (uint32_t gg = 0; gg < groups; gg++) { + for (uint32_t hh = 0; hh < host_groups[gg].size(); hh++) { + PLHostRecord *p = host_groups[gg][hh].get(); + if (p->available) { + PL_NH_Debug(PL_NH_DEBUG_TAG, "[%" PRIu64 "] found available next hop %.*s", sm_id, int(p->hostname.size()), + p->hostname.c_str()); + return true; + } + } + } + return false; +} + +bool +PLNextHopSelectionStrategy::codeIsFailure(TSHttpStatus response_code) +{ + return this->resp_codes.contains(response_code); +} + +bool +PLNextHopSelectionStrategy::responseIsRetryable(unsigned int current_retry_attempts, TSHttpStatus response_code) +{ + return this->codeIsFailure(response_code) && current_retry_attempts < this->max_simple_retries && + current_retry_attempts < this->num_parents; +} + +bool +PLNextHopSelectionStrategy::onFailureMarkParentDown(TSHttpStatus response_code) +{ + return static_cast(response_code) >= 500 && static_cast(response_code) <= 599; +} + +bool +PLNextHopSelectionStrategy::goDirect() +{ + PL_NH_Debug(PL_NH_DEBUG_TAG, "nhplugin goDirect calling"); + return this->go_direct; +} + +bool +PLNextHopSelectionStrategy::parentIsProxy() +{ + PL_NH_Debug(PL_NH_DEBUG_TAG, "nhplugin parentIsProxy calling"); + return this->parent_is_proxy; +} + +namespace YAML +{ +template <> struct convert { + static bool + decode(const Node &node, PLHostRecord &nh) + { + YAML::Node nd; + bool merge_tag_used = false; + + // check for YAML merge tag. + if (node["<<"]) { + nd = node["<<"]; + merge_tag_used = true; + } else { + nd = node; + } + + // lookup the hostname + if (nd["host"]) { + nh.hostname = nd["host"].Scalar(); + } else { + throw std::invalid_argument("Invalid host definition, missing host name."); + } + + // lookup the port numbers supported by this host. + YAML::Node proto = nd["protocol"]; + + if (proto.Type() != YAML::NodeType::Sequence) { + throw std::invalid_argument("Invalid host protocol definition, expected a sequence."); + } else { + for (unsigned int ii = 0; ii < proto.size(); ii++) { + YAML::Node protocol_node = proto[ii]; + std::shared_ptr pr = std::make_shared(protocol_node.as()); + nh.protocols.push_back(std::move(pr)); + } + } + + // get the host's weight + YAML::Node weight; + if (merge_tag_used) { + weight = node["weight"]; + nh.weight = weight.as(); + } else if ((weight = nd["weight"])) { + nh.weight = weight.as(); + } else { + PL_NH_Note("No weight is defined for the host '%s', using default 1.0", nh.hostname.data()); + nh.weight = 1.0; + } + + // get the host's optional hash_string + YAML::Node hash; + if ((hash = nd["hash_string"])) { + nh.hash_string = hash.Scalar(); + } + + return true; + } +}; + +template <> struct convert { + static bool + decode(const Node &node, PLNHProtocol &nh) + { + if (node["scheme"]) { + if (node["scheme"].Scalar() == "http") { + nh.scheme = PL_NH_SCHEME_HTTP; + } else if (node["scheme"].Scalar() == "https") { + nh.scheme = PL_NH_SCHEME_HTTPS; + } else { + nh.scheme = PL_NH_SCHEME_NONE; + } + } + if (node["port"]) { + nh.port = node["port"].as(); + } + if (node["health_check_url"]) { + nh.health_check_url = node["health_check_url"].Scalar(); + } + return true; + } +}; +}; // namespace YAML diff --git a/plugins/experimental/parent_select/strategy.h b/plugins/experimental/parent_select/strategy.h new file mode 100644 index 00000000000..df15514600a --- /dev/null +++ b/plugins/experimental/parent_select/strategy.h @@ -0,0 +1,271 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tscore/ConsistentHash.h" +#include "ts/ts.h" +#include "ts/parentselectdefs.h" +#include "ts/remap.h" +#include "healthstatus.h" + +// TODO rename, move to respective sub-plugins +#define PLUGIN_NAME "pparent_select" + +constexpr const char *PL_NH_DEBUG_TAG = "plugin_nexthop"; + +#define PL_NH_Debug(tag, fmt, ...) TSDebug(tag, "[%s:%d]: " fmt, __FILE__, __LINE__, ##__VA_ARGS__) +#define PL_NH_Error(fmt, ...) TSError("(%s) [%s:%d]: " fmt, PLUGIN_NAME, __FILE__, __LINE__, ##__VA_ARGS__) +#define PL_NH_Note(fmt, ...) TSDebug(PL_NH_DEBUG_TAG, "[%s:%d]: " fmt, __FILE__, __LINE__, ##__VA_ARGS__) + +constexpr const char *policy_strings[] = {"PL_NH_UNDEFINED", "PL_NH_FIRST_LIVE", "PL_NH_RR_STRICT", + "PL_NH_RR_IP", "PL_NH_RR_LATCHED", "PL_NH_CONSISTENT_HASH"}; + +constexpr const TSHttpStatus STATUS_CONNECTION_FAILURE = static_cast(0); + +enum PLNHPolicyType { + PL_NH_UNDEFINED = 0, + PL_NH_FIRST_LIVE, // first available nexthop + PL_NH_RR_STRICT, // strict round robin + PL_NH_RR_IP, // round robin by client ip. + PL_NH_RR_LATCHED, // latched to available next hop. + PL_NH_CONSISTENT_HASH, // consistent hashing strategy. + PL_NH_PLUGIN, // hashing strategy is a plugin +}; + +enum PLNHSchemeType { PL_NH_SCHEME_NONE = 0, PL_NH_SCHEME_HTTP, PL_NH_SCHEME_HTTPS }; + +enum PLNHRingMode { PL_NH_ALTERNATE_RING = 0, PL_NH_EXHAUST_RING }; + +// response codes container +struct PLResponseCodes { + PLResponseCodes(){}; + std::vector codes; + void + add(short code) + { + codes.push_back(code); + } + bool + contains(short code) + { + return std::binary_search(codes.begin(), codes.end(), code); + } + void + sort() + { + std::sort(codes.begin(), codes.end()); + } +}; + +struct PLHealthChecks { + bool active = false; + bool passive = false; +}; + +struct PLNHProtocol { + PLNHSchemeType scheme; + uint32_t port; + std::string health_check_url; +}; + +struct PLHostRecord : ATSConsistentHashNode { + std::mutex _mutex; + std::string hostname; + time_t failedAt; + uint32_t failCount; + time_t upAt; + float weight; + std::string hash_string; + int host_index; + int group_index; + std::vector> protocols; + + // construct without locking the _mutex. + PLHostRecord() + { + hostname = ""; + failedAt = 0; + failCount = 0; + upAt = 0; + weight = 0; + hash_string = ""; + host_index = -1; + group_index = -1; + available = true; + } + + // copy constructor to avoid copying the _mutex. + PLHostRecord(const PLHostRecord &o) + { + hostname = o.hostname; + failedAt = o.failedAt; + failCount = o.failCount; + upAt = o.upAt; + weight = o.weight; + hash_string = o.hash_string; + host_index = -1; + group_index = -1; + available = true; + protocols = o.protocols; + } + + // assign without copying the _mutex. + PLHostRecord & + operator=(const PLHostRecord &o) + { + hostname = o.hostname; + failedAt = o.failedAt; + upAt = o.upAt; + weight = o.weight; + hash_string = o.hash_string; + host_index = o.host_index; + group_index = o.group_index; + available = o.available.load(); + protocols = o.protocols; + return *this; + } + + // locks the record when marking this host down. + void + set_unavailable() + { + if (available) { + std::lock_guard lock(_mutex); + failedAt = time(nullptr); + available = false; + } + } + + // locks the record when marking this host up. + void + set_available() + { + if (!available) { + std::lock_guard lock(_mutex); + failedAt = 0; + failCount = 0; + upAt = time(nullptr); + available = true; + } + } + + int + getPort(PLNHSchemeType scheme) const + { + int port = 0; + for (uint32_t i = 0; i < protocols.size(); i++) { + if (protocols[i]->scheme == scheme) { + port = protocols[i]->port; + break; + } + } + return port; + } + + static std::string + makeHostPort(const std::string_view hostname, const in_port_t port) + { + return std::string(hostname) + ":" + std::to_string(port); + } + + std::string + getHostPort(const in_port_t port) const + { + return makeHostPort(this->hostname, port); + } +}; + +class TSNextHopSelectionStrategy +{ +public: + TSNextHopSelectionStrategy(){}; + virtual ~TSNextHopSelectionStrategy(){}; + + virtual const char *name() = 0; + virtual void next(TSHttpTxn txnp, void *strategyTxn, const char *exclude_hostname, size_t exclude_hostname_len, + in_port_t exclude_port, const char **out_hostname, size_t *out_hostname_len, in_port_t *out_port, + bool *out_retry, time_t now = 0) = 0; + virtual void mark(TSHttpTxn txnp, void *strategyTxn, const char *hostname, const size_t hostname_len, const in_port_t port, + const PLNHCmd status, const time_t now = 0) = 0; + virtual bool nextHopExists(TSHttpTxn txnp) = 0; + virtual bool codeIsFailure(TSHttpStatus response_code) = 0; + virtual bool responseIsRetryable(unsigned int current_retry_attempts, TSHttpStatus response_code) = 0; + virtual bool onFailureMarkParentDown(TSHttpStatus response_code) = 0; + + virtual bool goDirect() = 0; + virtual bool parentIsProxy() = 0; + + virtual void *newTxn() = 0; + virtual void deleteTxn(void *state) = 0; +}; + +class PLNextHopSelectionStrategy : public TSNextHopSelectionStrategy +{ +public: + PLNextHopSelectionStrategy(); + PLNextHopSelectionStrategy(const std::string_view &name); + virtual ~PLNextHopSelectionStrategy(){}; + bool Init(const YAML::Node &n); + + virtual void next(TSHttpTxn txnp, void *strategyTxn, const char *exclude_hostname, size_t exclude_hostname_len, + in_port_t exclude_port, const char **out_hostname, size_t *out_hostname_len, in_port_t *out_port, + bool *out_retry, time_t now = 0) = 0; + virtual void mark(TSHttpTxn txnp, void *strategyTxn, const char *hostname, const size_t hostname_len, const in_port_t port, + const PLNHCmd status, const time_t now = 0) = 0; + virtual bool nextHopExists(TSHttpTxn txnp); + virtual bool codeIsFailure(TSHttpStatus response_code); + virtual bool responseIsRetryable(unsigned int current_retry_attempts, TSHttpStatus response_code); + virtual bool onFailureMarkParentDown(TSHttpStatus response_code); + virtual bool goDirect(); + virtual bool parentIsProxy(); + virtual const char * + name() + { + return strategy_name.c_str(); + }; + virtual void *newTxn() = 0; + virtual void deleteTxn(void *state) = 0; + +protected: + std::string strategy_name; + bool go_direct = true; + bool parent_is_proxy = true; + bool ignore_self_detect = false; + PLNHSchemeType scheme = PL_NH_SCHEME_NONE; + PLNHRingMode ring_mode = PL_NH_ALTERNATE_RING; + PLResponseCodes resp_codes; + PLHealthChecks health_checks; + PLNextHopHealthStatus passive_health; + std::vector>> host_groups; + uint32_t max_simple_retries = 1; + uint32_t groups = 0; + uint32_t grp_index = 0; + uint32_t hst_index = 0; + uint32_t num_parents = 0; + uint32_t distance = 0; // index into the strategies list. +}; diff --git a/plugins/experimental/parent_select/util.h b/plugins/experimental/parent_select/util.h new file mode 100644 index 00000000000..fa050b009bc --- /dev/null +++ b/plugins/experimental/parent_select/util.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#pragma once + +// ScopedFreeMLoc frees the given TSMLoc with TSHandleMLocRelease(buf, parent, *m) when it goes out of scope. +// The lifetime of buf and parent must exceed this. +// The parent must be allocated before this (if it exists). +// The parent may be TS_NULL_MLOC. +// If mloc is set to TS_NULL_MLOC or never allocated, it will not be freed. +struct ScopedFreeMLoc { + ScopedFreeMLoc(TSMBuffer *_buf, TSMLoc _parent, TSMLoc *_mloc) : mloc(_mloc), parent(_parent), buf(_buf){}; + ~ScopedFreeMLoc() + { + if (*mloc != TS_NULL_MLOC) { + TSHandleMLocRelease(*buf, parent, *mloc); + } + }; + +private: + TSMLoc *mloc; + TSMLoc parent; + TSMBuffer *buf; +}; + +// StrVal is a string as returned by TSUrlStringGet and other TS API functions. +// Zeroes on initialization. +struct StrVal { + StrVal() : ptr(nullptr), len(0){}; + char *ptr; + int len; +}; + +// ScopedFreeStrVal frees the ptr in the given Strval when it goes out of scope. +struct ScopedFreeStrVal { + ScopedFreeStrVal(StrVal *_strval) : strval(_strval){}; + ~ScopedFreeStrVal() { TSfree(strval->ptr); }; + +private: + StrVal *strval; +}; diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index f1257627b7d..c49c0b3c79a 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -658,7 +658,6 @@ find_server_and_update_current_info(HttpTransact::State *s) // if the configuration does not allow the origin to be dns'd // we're unable to go direct to the origin. if (s->http_config_param->no_dns_forward_to_parent) { - TxnDebug("http_trans", "find_server_and_update_current_info result parented, no dns, setting FAIL and HOST_NONE"); Warning("no available parents and the config proxy.config.http.no_dns_just_forward_to_parent, prevents origin lookups."); s->parent_result.result = PARENT_FAIL; return HttpTransact::HOST_NONE;