diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst b/doc/admin-guide/plugins/header_rewrite.en.rst index 17113379643..e7f8c74769c 100644 --- a/doc/admin-guide/plugins/header_rewrite.en.rst +++ b/doc/admin-guide/plugins/header_rewrite.en.rst @@ -647,6 +647,7 @@ are supported:: %{NEXT-HOP:HOST} Name of the current selected parent. %{NEXT-HOP:PORT} Port of the current selected parent. + %{NEXT-HOP:STRATEGY} Name of the current strategy (can be "" if not using a strategy) Note that the ```` of NEXT-HOP will likely not be available unless an origin server connection is attempted at which point it will available @@ -1083,6 +1084,18 @@ if necessary. The header's ```` may be a literal string, or take advantage of `String concatenations`_ to calculate a dynamic value for the header. +set-next-hop-strategy +~~~~~~~~~~~~~~~~~~~~~ +:: + + set-next-hop-strategy + +Replaces/Sets the current next hop parent selection strategy with +the matching strategy specified in `strategies.yaml` + +Setting to "null" removes the current strategy which will fall back +to other methods (ie: parent.config or remap to url). + set-redirect ~~~~~~~~~~~~ :: diff --git a/doc/admin-guide/plugins/lua.en.rst b/doc/admin-guide/plugins/lua.en.rst index d065d3d1e33..aba1ff7db3d 100644 --- a/doc/admin-guide/plugins/lua.en.rst +++ b/doc/admin-guide/plugins/lua.en.rst @@ -1930,6 +1930,50 @@ Here is an example: `TOP <#ts-lua-plugin>`_ +ts.http.get_next_hop_strategy +----------------------------- +**syntax:** *ts.http.get_next_hop_strategy()* + +**context:** function @ TS_LUA_HOOK_READ_REQUEST_HDR or do_remap() + +**description** Returns the name of the current next hop selection strategy, or nil string if no strategy is in use. + +Here is an example: + +:: + + function do_remap() + local strategy = ts.http.get_next_hop_strategy() + ts.debug("Using strategy: " .. strategy) + end + +`TOP <#ts-lua-plugin>`_ + +ts.http.set_next_hop_strategy +----------------------------- +**syntax:** *ts.http.set_next_hop_strategy(str)* + +**context:** function @ TS_LUA_HOOK_READ_REQUEST_HDR or do_remap() + +**description** Looks for the named strategy and sets the current +transaction to use that strategy. + +Use empty string or "null" to clear the strategy and fall back to +parent.config or the remap to url. + +Here is an example: + +:: + + function do_remap() + local uri = ts.client_request.get_uri() + if uri == "otherhost" then + ts.http.set_next_hop_strategy("otherhost") + end + end + +`TOP <#ts-lua-plugin>`_ + ts.sha256 --------- **syntax:** *digest = ts.sha256(str)* diff --git a/doc/admin-guide/plugins/regex_remap.en.rst b/doc/admin-guide/plugins/regex_remap.en.rst index 547309faca8..b309ca07267 100644 --- a/doc/admin-guide/plugins/regex_remap.en.rst +++ b/doc/admin-guide/plugins/regex_remap.en.rst @@ -143,6 +143,7 @@ remap.config. The following options are available :: @caseless - Make regular expressions case insensitive @lowercase_substitutions - Turn on (enable) lower case substitutions + @strategy - Specify a strategy from strategies.yaml. "null" or "" will clear the strategy. This can be useful to force a particular response for some URLs, e.g. :: diff --git a/doc/developer-guide/api/functions/TSHttpNextHopStrategyNameGet.en.rst b/doc/developer-guide/api/functions/TSHttpNextHopStrategyNameGet.en.rst new file mode 100644 index 00000000000..273b636fb82 --- /dev/null +++ b/doc/developer-guide/api/functions/TSHttpNextHopStrategyNameGet.en.rst @@ -0,0 +1,49 @@ +.. 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:: ../../../common.defs + +.. default-domain:: cpp + +TSHttpNextHopStrategyNameGet +**************************** + +Synopsis +======== + +.. code-block:: cpp + + #include + +.. function:: char const* TSHttpNextHopStrategyNameGet(void const* strategy) + +Description +=========== + +Gets the name associated with the provided strategy. +This may be nullptr indicating that parent.config is in use. + +.. note:: + + This returned pointer must not be freed and the contents must not + be changed. + Strategy pointers held by plugins will become invalid when ATS + configs are reloaded and should be reset with :func:`TSRemapNewInstance` + +See Also +======== + +:func:`TSHttpTxnNextHopStrategyGet` diff --git a/doc/developer-guide/api/functions/TSHttpTxnNextHopNamedStrategyGet.en.rst b/doc/developer-guide/api/functions/TSHttpTxnNextHopNamedStrategyGet.en.rst new file mode 100644 index 00000000000..949d92346dc --- /dev/null +++ b/doc/developer-guide/api/functions/TSHttpTxnNextHopNamedStrategyGet.en.rst @@ -0,0 +1,52 @@ +.. 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:: ../../../common.defs + +.. default-domain:: cpp + +TSHttpTxnNextHopNamedStrategyGet +******************************** + +Synopsis +======== + +.. code-block:: cpp + + #include + +.. function:: void const* TSHttpTxnNextHopNamedStrategyGet(TSHttpTxn txnp, const char *name) + +Description +=========== + +Gets a pointer to the specified :arg:`name` NextHopSelectionStrategy. +This may be nullptr indicating that no strategy exists with the given name. + +This function uses the transaction :arg:`txnp` to get access to the +NextHopStrategyFactory associated with the current configuration. + +.. note:: + + This returned pointer must not be freed and the contents must not + be changed. + Strategy pointers held by plugins will become invalid when ATS + configs are reloaded and should be reset with :func:`TSRemapNewInstance` + +See Also +======== + +:func:`TSHttpTxnNextHopStrategySet` diff --git a/doc/developer-guide/api/functions/TSHttpTxnNextHopStrategyGet.en.rst b/doc/developer-guide/api/functions/TSHttpTxnNextHopStrategyGet.en.rst new file mode 100644 index 00000000000..c609511bbe5 --- /dev/null +++ b/doc/developer-guide/api/functions/TSHttpTxnNextHopStrategyGet.en.rst @@ -0,0 +1,49 @@ +.. 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:: ../../../common.defs + +.. default-domain:: cpp + +TSHttpTxnNextHopStrategyGet +*************************** + +Synopsis +======== + +.. code-block:: cpp + + #include + +.. function:: void const* TSHttpTxnNextHopStrategyGet(TSHttpTxn txnp) + +Description +=========== + +Gets a pointer to the current transaction :arg:`txnp` NextHopSelectionStrategy. +This may be nullptr indicating that parent.config is in use. + +.. note:: + + This strategy pointer must not be freed and the contents must not + be changed. + Strategy pointers held by plugins will become invalid when ATS + configs are reloaded and should be reset with :func:`TSRemapNewInstance` + +See Also +======== + +:func:`TSHttpTxnNextHopStrategySet` diff --git a/doc/developer-guide/api/functions/TSHttpTxnNextHopStrategySet.en.rst b/doc/developer-guide/api/functions/TSHttpTxnNextHopStrategySet.en.rst new file mode 100644 index 00000000000..33ca4b6266a --- /dev/null +++ b/doc/developer-guide/api/functions/TSHttpTxnNextHopStrategySet.en.rst @@ -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. + +.. include:: ../../../common.defs + +.. default-domain:: cpp + +TSHttpTxnNextHopNameGet +*********************** + +Synopsis +======== + +.. code-block:: cpp + + #include + +.. function:: void TSHttpTxnNextHopStrategySet(TSHttpTxn txnp, void const* strategy) + +Description +=========== + +Sets the next hop strategy for the transaction :arg:`txnp` +This :arg:`strategy` pointer must be a valid strategy and can be +nullptr to indicate that parent.config will be used instead. + +Plugins can get a strategy by name by calling +:func:`TSHttpTxnNextHopStrategyGet` to get the current transaction's +active strategy or :func:`TSHttpTxnNextHopNamedStrategyGet` to +look up a strategy by name using the transaction's pointer to the +NextHopStrategyFactory strategy database. + +.. note:: + + This strategy pointer must not be freed and the contents must not + be changed. + Strategy pointers held by plugins will become invalid when ATS + configs are reloaded and should be reset with :func:`TSRemapNewInstance` + +See Also +======== + +:func:`TSHttpTxnNextHopStrategyGet`, :func:`TSHttpTxnNextHopNamedStrategyGet`. diff --git a/include/proxy/http/HttpTransact.h b/include/proxy/http/HttpTransact.h index 66e53a889d1..8d0fea68c2a 100644 --- a/include/proxy/http/HttpTransact.h +++ b/include/proxy/http/HttpTransact.h @@ -724,11 +724,11 @@ class HttpTransact // able to defer some work in building the request TransactFunc_t pending_work = nullptr; - HttpRequestData request_data; - ParentConfigParams *parent_params = nullptr; - std::shared_ptr next_hop_strategy = nullptr; - ParentResult parent_result; - CacheControlResult cache_control; + HttpRequestData request_data; + ParentConfigParams *parent_params = nullptr; + NextHopSelectionStrategy *next_hop_strategy = nullptr; + ParentResult parent_result; + CacheControlResult cache_control; StateMachineAction_t next_action = StateMachineAction_t::UNDEFINED; // out StateMachineAction_t api_next_action = StateMachineAction_t::UNDEFINED; // out diff --git a/include/proxy/http/remap/NextHopStrategyFactory.h b/include/proxy/http/remap/NextHopStrategyFactory.h index 4822460d914..aae01e3d1b9 100644 --- a/include/proxy/http/remap/NextHopStrategyFactory.h +++ b/include/proxy/http/remap/NextHopStrategyFactory.h @@ -45,7 +45,10 @@ class NextHopStrategyFactory NextHopStrategyFactory() = delete; NextHopStrategyFactory(const char *file); ~NextHopStrategyFactory(); - std::shared_ptr strategyInstance(const char *name); + + // The lifetime of this is attached to UrlRewrite which is reference + // counted and attached to an HttpSM. + NextHopSelectionStrategy *strategyInstance(const char *name) const; bool strategies_loaded; @@ -53,5 +56,5 @@ class NextHopStrategyFactory std::string fn; void loadConfigFile(const std::string &file, std::stringstream &doc, std::unordered_set &include_once); void createStrategy(const std::string &name, const NHPolicyType policy_type, ts::Yaml::Map &node); - std::unordered_map> _strategies; + std::unordered_map _strategies; }; diff --git a/include/proxy/http/remap/UrlMapping.h b/include/proxy/http/remap/UrlMapping.h index a35b66f8c12..dabab071185 100644 --- a/include/proxy/http/remap/UrlMapping.h +++ b/include/proxy/http/remap/UrlMapping.h @@ -112,9 +112,9 @@ class url_mapping bool ip_allow_check_enabled_p = false; acl_filter_rule *filter = nullptr; // acl filtering (linked list of rules) LINK(url_mapping, link); // For use with the main Queue linked list holding all the mapping - std::shared_ptr strategy = nullptr; - std::string remapKey; - std::atomic _hitCount = 0; // counter can overflow + NextHopSelectionStrategy *strategy = nullptr; + std::string remapKey; + std::atomic _hitCount = 0; // counter can overflow int getRank() const diff --git a/include/ts/ts.h b/include/ts/ts.h index 3bd473fe11a..048f69b93dd 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -1576,6 +1576,67 @@ void TSHttpTxnErrorBodySet(TSHttpTxn txnp, char *buf, size_t buflength, char *mi */ char *TSHttpTxnErrorBodyGet(TSHttpTxn txnp, size_t *buflength, char **mimetype); +/** + Sets the Transaction's Next Hop Parent Strategy. + Calling this after TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK will + result in bad behavior. + + You can get this strategy pointer by calling TSHttpTxnParentStrategyGet(). + + @param txnp HTTP transaction whose parent strategy to set. + @param pointer to the given strategy. + + */ +void TSHttpTxnNextHopStrategySet(TSHttpTxn txnp, void const *strategy); + +/** + Retrieves a pointer to the current next hop selection strategy. + This value may be a nullptr due to: + - parent proxying not enabled + - no parent selection strategy (using parent.config) + + @param txnp HTTP transaction whose next hop strategy to get. + + */ +void const *TSHttpTxnNextHopStrategyGet(TSHttpTxn txnp); + +/** + Returns either null pointer or null terminated pointer to name. + DO NOT FREE. + + This value may be a nullptr due to: + - parent proxying not enabled + - no parent selection strategy (using parent.config) + + @param txnp HTTP transaction whose next hop strategy to get. + + */ +char const *TSHttpNextHopStrategyNameGet(void const *strategy); + +/** + Retrieves a pointer to the named strategy in the strategy table. + Returns nullptr if no strategy is set. + This uses the current transaction's state machine to get + access to UrlRewrite's NextHopStrategyFactory. + + @param txnp HTTP transaction which holds the strategy table. + @param name of the strategy to look up. + + */ +void const *TSHttpTxnNextHopNamedStrategyGet(TSHttpTxn txnp, const char *name); + +/** + Sets the parent proxy name and port. The string hostname is copied + into the TSHttpTxn; you can modify or delete the string after + calling TSHttpTxnParentProxySet(). + + @param txnp HTTP transaction whose parent proxy to set. + @param hostname parent proxy host name string. + @param port parent proxy port to set. + + */ +void TSHttpTxnParentProxySet(TSHttpTxn txnp, const char *hostname, int port); + /** Retrieves the parent proxy hostname and port, if parent proxying is enabled. If parent proxying is not enabled, diff --git a/plugins/header_rewrite/conditions.cc b/plugins/header_rewrite/conditions.cc index 696088e03db..a9c1b759e99 100644 --- a/plugins/header_rewrite/conditions.cc +++ b/plugins/header_rewrite/conditions.cc @@ -1538,6 +1538,15 @@ ConditionNextHop::append_value(std::string &s, const Resources &res) Dbg(pi_dbg_ctl, "Appending '%d' to evaluation value", port); s.append(std::to_string(port)); } break; + case NEXT_HOP_STRATEGY: { + char const *const name = TSHttpNextHopStrategyNameGet(res.state.txnp); + if (nullptr != name) { + Dbg(pi_dbg_ctl, "Appending '%s' to evaluation value", name); + s.append(name); + } else { + Dbg(pi_dbg_ctl, "NextHopStrategyName is empty"); + } + } break; default: TSReleaseAssert(!"All cases should have been handled"); break; diff --git a/plugins/header_rewrite/factory.cc b/plugins/header_rewrite/factory.cc index 3317b49b335..9e0499f060e 100644 --- a/plugins/header_rewrite/factory.cc +++ b/plugins/header_rewrite/factory.cc @@ -89,6 +89,8 @@ operator_factory(const std::string &op) o = new OperatorSetStateInt16(); } else if (op == "set-effective-address") { o = new OperatorSetEffectiveAddress(); + } else if (op == "set-next-hop-strategy") { + o = new OperatorSetNextHopStrategy(); } else { TSError("[%s] Unknown operator: %s", PLUGIN_NAME, op.c_str()); return nullptr; diff --git a/plugins/header_rewrite/operators.cc b/plugins/header_rewrite/operators.cc index cb7ac53c12f..02d031db3db 100644 --- a/plugins/header_rewrite/operators.cc +++ b/plugins/header_rewrite/operators.cc @@ -1627,3 +1627,51 @@ OperatorSetEffectiveAddress::exec(const Resources &res) const return true; } + +// OperatorSetNextHopStrategy +void +OperatorSetNextHopStrategy::initialize(Parser &p) +{ + Operator::initialize(p); + + _value.set_value(p.get_arg(), this); + Dbg(pi_dbg_ctl, "OperatorSetNextHopStrategy::initialie: %s", _value.get_value().c_str()); +} + +void +OperatorSetNextHopStrategy::initialize_hooks() +{ + add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); + add_allowed_hook(TS_REMAP_PSEUDO_HOOK); +} + +bool +OperatorSetNextHopStrategy::exec(const Resources &res) const +{ + if (!res.state.txnp) { + TSError("[%s] OperatorSetNextHopStrategy() failed. Transaction is null", PLUGIN_NAME); + } + + auto const txnp = res.state.txnp; + + std::string value; + _value.append_value(value, res); + + // Setting an empty strategy clears it for either parent.config or remap to + if ("null" == value || value.empty()) { + Dbg(pi_dbg_ctl, "Clearing strategy"); + TSHttpTxnNextHopStrategySet(txnp, nullptr); + return true; + } + + void const *const stratptr = TSHttpTxnNextHopNamedStrategyGet(txnp, value.c_str()); + if (nullptr == stratptr) { + TSWarning("[%s] Failed to get strategy '%s'", PLUGIN_NAME, value.c_str()); + return false; + } else { + Dbg(pi_dbg_ctl, " Setting strategy '%s'", value.c_str()); + TSHttpTxnNextHopStrategySet(txnp, stratptr); + } + + return true; +} diff --git a/plugins/header_rewrite/operators.h b/plugins/header_rewrite/operators.h index 4ff4f0edcbc..a0b825d3d05 100644 --- a/plugins/header_rewrite/operators.h +++ b/plugins/header_rewrite/operators.h @@ -651,3 +651,22 @@ class OperatorSetEffectiveAddress : public Operator private: Value _value; }; + +class OperatorSetNextHopStrategy : public Operator +{ +public: + OperatorSetNextHopStrategy() { Dbg(dbg_ctl, "Calling CTOR for OperatorSetNextHopStrategy"); } + + // noncopyable + OperatorSetNextHopStrategy(const OperatorSetNextHopStrategy &) = delete; + void operator=(const OperatorSetNextHopStrategy &) = delete; + + void initialize(Parser &p) override; + +protected: + void initialize_hooks() override; + bool exec(const Resources &res) const override; + +private: + Value _value; +}; diff --git a/plugins/header_rewrite/statement.cc b/plugins/header_rewrite/statement.cc index 979eb8f146e..7fc39503d0d 100644 --- a/plugins/header_rewrite/statement.cc +++ b/plugins/header_rewrite/statement.cc @@ -127,6 +127,8 @@ Statement::parse_next_hop_qualifier(const std::string &q) const qual = NEXT_HOP_HOST; } else if (q == "PORT") { qual = NEXT_HOP_PORT; + } else if (q == "STRATEGY") { + qual = NEXT_HOP_STRATEGY; } else { TSError("[%s] Invalid NextHop() qualifier: %s", PLUGIN_NAME, q.c_str()); } diff --git a/plugins/header_rewrite/statement.h b/plugins/header_rewrite/statement.h index bb7a7966871..137c60413cf 100644 --- a/plugins/header_rewrite/statement.h +++ b/plugins/header_rewrite/statement.h @@ -63,6 +63,7 @@ enum NextHopQualifiers { NEXT_HOP_NONE, NEXT_HOP_HOST, NEXT_HOP_PORT, + NEXT_HOP_STRATEGY, }; // NOW data diff --git a/plugins/lua/ts_lua_http.cc b/plugins/lua/ts_lua_http.cc index 4326224f782..1bb486093b4 100644 --- a/plugins/lua/ts_lua_http.cc +++ b/plugins/lua/ts_lua_http.cc @@ -75,6 +75,10 @@ static int ts_lua_http_set_cache_url(lua_State *L); static int ts_lua_http_get_cache_lookup_url(lua_State *L); static int ts_lua_http_set_cache_lookup_url(lua_State *L); static int ts_lua_http_redo_cache_lookup(lua_State *L); + +static int ts_lua_http_get_next_hop_strategy(lua_State *L); +static int ts_lua_http_set_next_hop_strategy(lua_State *L); + static int ts_lua_http_get_parent_proxy(lua_State *L); static int ts_lua_http_set_parent_proxy(lua_State *L); static int ts_lua_http_get_parent_selection_url(lua_State *L); @@ -180,6 +184,12 @@ ts_lua_inject_http_cache_api(lua_State *L) lua_pushcfunction(L, ts_lua_http_redo_cache_lookup); lua_setfield(L, -2, "redo_cache_lookup"); + lua_pushcfunction(L, ts_lua_http_get_next_hop_strategy); + lua_setfield(L, -2, "get_next_hop_strategy"); + + lua_pushcfunction(L, ts_lua_http_set_next_hop_strategy); + lua_setfield(L, -2, "set_next_hop_strategy"); + lua_pushcfunction(L, ts_lua_http_get_parent_proxy); lua_setfield(L, -2, "get_parent_proxy"); @@ -517,6 +527,62 @@ ts_lua_http_redo_cache_lookup(lua_State *L) return 0; } +static int +ts_lua_http_get_next_hop_strategy(lua_State *L) +{ + char const *name = nullptr; + ts_lua_http_ctx *http_ctx; + + GET_HTTP_CONTEXT(http_ctx, L); + + void const *const stratptr = TSHttpTxnNextHopStrategyGet(http_ctx->txnp); + if (nullptr != stratptr) { + name = TSHttpNextHopStrategyNameGet(stratptr); + } + + if (name == nullptr) { + lua_pushnil(L); + } else { + lua_pushstring(L, name); + } + + return 1; +} + +static int +ts_lua_http_set_next_hop_strategy(lua_State *L) +{ + int n = 0; + + ts_lua_http_ctx *http_ctx; + + GET_HTTP_CONTEXT(http_ctx, L); + + n = lua_gettop(L); + + if (n == 1) { + const char *name = nullptr; + size_t name_len; + + name = luaL_checklstring(L, 1, &name_len); + if (0 == name_len || "null" == std::string_view(name)) { + Dbg(dbg_ctl, "Clearning strategy (use parent.config)"); + TSHttpTxnNextHopStrategySet(http_ctx->txnp, nullptr); + } else { + void const *const stratptr = TSHttpTxnNextHopNamedStrategyGet(http_ctx->txnp, name); + if (nullptr == stratptr) { + TSError("[ts_lua][%s] Failed get next hop strategy name '%s'", __FUNCTION__, name); + } else { + TSHttpTxnNextHopStrategySet(http_ctx->txnp, stratptr); + } + } + } else { + return luaL_error(L, "incorrect # of arguments for set_parent_proxy, receiving %d instead of 1", n); + } + + return 0; +} + static int ts_lua_http_get_parent_proxy(lua_State *L) { diff --git a/plugins/regex_remap/regex_remap.cc b/plugins/regex_remap/regex_remap.cc index ca838c23ef1..03a293c93ca 100644 --- a/plugins/regex_remap/regex_remap.cc +++ b/plugins/regex_remap/regex_remap.cc @@ -226,6 +226,11 @@ class RemapRegex { return _lowercase_substitutions; } + inline std::string const & + strategy() const + { + return _strategy; + } // Hold an overridable configurations struct Override { @@ -263,6 +268,8 @@ class RemapRegex int _connect_timeout = -1; int _dns_timeout = -1; + std::string _strategy = {}; + Override *_first_override = nullptr; int _sub_pos[MAX_SUBS]; int _sub_ix[MAX_SUBS]; @@ -309,6 +316,8 @@ RemapRegex::initialize(const std::string ®, const std::string &sub, const std _options |= PCRE_CASELESS; } else if (opt.compare(start, 23, "lowercase_substitutions") == 0) { _lowercase_substitutions = true; + } else if (opt.compare(start, 8, "strategy") == 0) { + _strategy = opt_val; } else if (opt_val.size() <= 0) { // All other options have a required value TSError("[%s] Malformed options: %s", PLUGIN_NAME, opt.c_str()); @@ -971,6 +980,19 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri) Dbg(dbg_ctl, "Setting DNS timeout to %d", re->dns_timeout_option()); TSHttpTxnDNSTimeoutSet(txnp, re->dns_timeout_option()); } + auto const &strat = re->strategy(); + if (strat.empty() || "null" == strat) { + Dbg(dbg_ctl, "Clearing strategy (use parent.config)"); + TSHttpTxnNextHopStrategySet(txnp, nullptr); + } else { + void const *const stratptr = TSHttpTxnNextHopNamedStrategyGet(txnp, strat.c_str()); + if (nullptr == stratptr) { + Dbg(dbg_ctl, "No strategy found with name '%s'", strat.c_str()); + } else { + Dbg(dbg_ctl, "Setting strategy to %s", strat.c_str()); + TSHttpTxnNextHopStrategySet(txnp, stratptr); + } + } bool lowercase_substitutions = false; if (re->lowercase_substitutions_option() == true) { Dbg(dbg_ctl, "Setting lowercasing substitutions on"); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 41d8fc198ad..e7d7f71742b 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -4980,6 +4980,59 @@ TSHttpTxnServerRequestBodySet(TSHttpTxn txnp, char *buf, int64_t buflength) s->internal_msg_buffer_fast_allocator_size = -1; } +void const * +TSHttpTxnNextHopStrategyGet(TSHttpTxn txnp) +{ + sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS); + + auto sm = reinterpret_cast(txnp); + + return static_cast(sm->t_state.next_hop_strategy); +} + +void +TSHttpTxnNextHopStrategySet(TSHttpTxn txnp, void const *stratptr) +{ + sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS); + // null strategy falls back to parent.config + // sdk_assert(sdk_sanity_check_null_ptr(strategy) == TS_SUCCESS); + + auto sm = reinterpret_cast(txnp); + auto strategy = reinterpret_cast(stratptr); + + sm->t_state.next_hop_strategy = const_cast(strategy); +} + +char const * +TSHttpNextHopStrategyNameGet(void const *stratptr) +{ + char const *name = nullptr; + if (nullptr != stratptr) { + auto strategy = reinterpret_cast(stratptr); + name = strategy->strategy_name.c_str(); + } + + return name; +} + +void const * +TSHttpTxnNextHopNamedStrategyGet(TSHttpTxn txnp, const char *name) +{ + sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS); + sdk_assert(sdk_sanity_check_null_ptr((void *)name) == TS_SUCCESS); + + auto sm = reinterpret_cast(txnp); + + sdk_assert(sdk_sanity_check_null_ptr((void *)sm->m_remap) == TS_SUCCESS); + sdk_assert(sdk_sanity_check_null_ptr((void *)sm->m_remap->strategyFactory) == TS_SUCCESS); + + // HttpSM has a reference count handle to UrlRewrite which has a + // pointer to NextHopStrategyFactory + NextHopSelectionStrategy const *const strat = sm->m_remap->strategyFactory->strategyInstance(name); + + return static_cast(strat); +} + TSReturnCode TSHttpTxnParentProxyGet(TSHttpTxn txnp, const char **hostname, int *port) { diff --git a/src/api/InkAPITest.cc b/src/api/InkAPITest.cc index a1dc169a743..521474bedda 100644 --- a/src/api/InkAPITest.cc +++ b/src/api/InkAPITest.cc @@ -3062,6 +3062,7 @@ struct SocketTest { bool test_next_hop_ip_get; bool test_next_hop_name_get; bool test_next_hop_port_get; + bool test_next_hop_strategy_get; bool test_client_protocol_stack_get; bool test_client_protocol_stack_contains; @@ -3156,6 +3157,28 @@ checkHttpTxnClientProtocolStackContains(SocketTest *test, void *data) return TS_EVENT_CONTINUE; } +// This func is called by us from mytest_handler to check for TSHttpTxnNextStrategyGet +static int +checkHttpTxnNextHopStrategyGet(SocketTest *test, void *data) +{ + TSHttpTxn txnp = static_cast(data); + + // this is an invalid pointer but the contents don't matter for this test. + void const *const exp = reinterpret_cast(0x01); + + void const *const strategy = TSHttpTxnNextHopStrategyGet(txnp); + if (strategy == exp) { + test->test_next_hop_strategy_get = true; + SDK_RPRINT(test->regtest, "TSHttpTxnNextHopStrategyGet", "TestCase1", TC_PASS, "ok"); + } else { + test->test_next_hop_strategy_get = false; + SDK_RPRINT(test->regtest, "TSHttpTxnNextHopStrategyGet", "TestCase1", TC_FAIL, "Value's Mismatch [expected '%jx', got '%jx'", + exp, strategy); + } + + return TS_EVENT_CONTINUE; +} + // This func is called by us from mytest_handler to check for TSHttpTxnNextHopIPGet static int checkHttpTxnNextHopIPGet(SocketTest *test, void *data) @@ -3485,6 +3508,11 @@ mytest_handler(TSCont contp, TSEvent event, void *data) test->hook_mask |= 2; } TSHttpTxnCntlSet(static_cast(data), TS_HTTP_CNTL_SKIP_REMAPPING, true); + + // Set the strategy pointer here + // this is an invalid pointer but the contents don't matter for this test. + TSHttpTxnNextHopStrategySet(static_cast(data), (void *)0x01); + checkHttpTxnClientReqGet(test, data); TSHttpTxnReenable(static_cast(data), TS_EVENT_HTTP_CONTINUE); @@ -3501,6 +3529,7 @@ mytest_handler(TSCont contp, TSEvent event, void *data) checkHttpTxnClientIPGet(test, data); checkHttpTxnServerIPGet(test, data); + checkHttpTxnNextHopStrategyGet(test, data); TSHttpTxnReenable(static_cast(data), TS_EVENT_HTTP_CONTINUE); test->reenable_mask |= 8; @@ -3591,7 +3620,7 @@ mytest_handler(TSCont contp, TSEvent event, void *data) (test->test_client_remote_port_get != true) || (test->test_client_req_get != true) || (test->test_client_resp_get != true) || (test->test_server_ip_get != true) || (test->test_server_req_get != true) || (test->test_server_resp_get != true) || (test->test_next_hop_ip_get != true) || (test->test_next_hop_name_get != true) || - (test->test_next_hop_port_get != true)) { + (test->test_next_hop_port_get != true) || (test->test_next_hop_strategy_get != true)) { *(test->pstatus) = REGRESSION_TEST_FAILED; } // transaction is over. clean up. @@ -3635,6 +3664,7 @@ EXCLUSIVE_REGRESSION_TEST(SDK_API_HttpHookAdd)(RegressionTest *test, int /* atyp socktest->test_next_hop_ip_get = false; socktest->test_next_hop_name_get = false; socktest->test_next_hop_port_get = false; + socktest->test_next_hop_strategy_get = false; socktest->magic = MAGIC_ALIVE; TSContDataSet(cont, socktest); diff --git a/src/proxy/http/HttpSM.cc b/src/proxy/http/HttpSM.cc index 7eceaa7e32a..cd1cdbb808f 100644 --- a/src/proxy/http/HttpSM.cc +++ b/src/proxy/http/HttpSM.cc @@ -314,8 +314,9 @@ HttpSM::init(bool from_early_data) // Added to skip dns if the document is in cache. DNS will be forced if there is a ip based ACL in // cache control or parent.config or if the doc_in_cache_skip_dns is disabled or if http caching is disabled // TODO: This probably doesn't honor this as a per-transaction overridable config. - t_state.force_dns = (ip_rule_in_CacheControlTable() || t_state.parent_params->parent_table->ipMatch || - !(t_state.txn_conf->doc_in_cache_skip_dns) || !(t_state.txn_conf->cache_http)); + t_state.force_dns = + (ip_rule_in_CacheControlTable() || (nullptr != t_state.parent_params && t_state.parent_params->parent_table->ipMatch) || + !(t_state.txn_conf->doc_in_cache_skip_dns) || !(t_state.txn_conf->cache_http)); SET_HANDLER(&HttpSM::main_handler); diff --git a/src/proxy/http/HttpTransact.cc b/src/proxy/http/HttpTransact.cc index 0fef997b91c..3dae0aa1ccf 100644 --- a/src/proxy/http/HttpTransact.cc +++ b/src/proxy/http/HttpTransact.cc @@ -128,13 +128,12 @@ extern HttpBodyFactory *body_factory; inline static bool bypass_ok(HttpTransact::State *s) { - url_mapping *mp = s->url_map.getMapping(); if (s->response_action.handled) { return s->response_action.action.goDirect; - } else if (mp && mp->strategy) { + } else if (nullptr != s->next_hop_strategy) { // remap strategies do not support the TSHttpTxnParentProxySet API. - return mp->strategy->go_direct; - } else if (s->parent_params) { + return s->next_hop_strategy->go_direct; + } else if (nullptr != s->parent_params) { return s->parent_result.bypass_ok(); } return false; @@ -145,13 +144,11 @@ bypass_ok(HttpTransact::State *s) inline static bool is_api_result(HttpTransact::State *s) { - bool r = false; - url_mapping *mp = s->url_map.getMapping(); - - if (mp && mp->strategy) { + bool r = false; + if (nullptr != s->next_hop_strategy) { // remap strategies do not support the TSHttpTxnParentProxySet API. r = false; - } else if (s->parent_params) { + } else if (nullptr != s->parent_params) { r = s->parent_result.is_api_result(); } return r; @@ -184,12 +181,11 @@ numParents(HttpTransact::State *s) inline static bool parent_is_proxy(HttpTransact::State *s) { - url_mapping *mp = s->url_map.getMapping(); 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) { + } else if (nullptr != s->next_hop_strategy) { + return s->next_hop_strategy->parent_is_proxy; + } else if (nullptr != s->parent_params) { return s->parent_result.parent_is_proxy(); } return false; @@ -211,7 +207,6 @@ retry_type(HttpTransact::State *s) inline static void findParent(HttpTransact::State *s) { - url_mapping *mp = s->url_map.getMapping(); Metrics::Counter::increment(http_rsb.parent_count); if (s->response_action.handled) { s->parent_result.hostname = s->response_action.action.hostname; @@ -224,9 +219,9 @@ findParent(HttpTransact::State *s) } else { s->parent_result.result = ParentResultType::FAIL; } - } else if (mp && mp->strategy) { - mp->strategy->findNextHop(reinterpret_cast(s->state_machine)); - } else if (s->parent_params) { + } else if (nullptr != s->next_hop_strategy) { + s->next_hop_strategy->findNextHop(reinterpret_cast(s->state_machine)); + } else if (nullptr != s->parent_params) { s->parent_params->findParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time); } @@ -237,8 +232,6 @@ findParent(HttpTransact::State *s) inline static void markParentDown(HttpTransact::State *s) { - url_mapping *mp = s->url_map.getMapping(); - TxnDbg(dbg_ctl_http_trans, "enable_parent_timeout_markdowns: %d, disable_parent_markdowns: %d", s->txn_conf->enable_parent_timeout_markdowns, s->txn_conf->disable_parent_markdowns); @@ -258,10 +251,10 @@ markParentDown(HttpTransact::State *s) 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, - NHCmd::MARK_DOWN); - } else if (s->parent_params) { + } else if (nullptr != s->next_hop_strategy) { + s->next_hop_strategy->markNextHop(reinterpret_cast(s->state_machine), s->parent_result.hostname, + s->parent_result.port, NHCmd::MARK_DOWN); + } else if (nullptr != s->parent_params) { s->parent_params->markParentDown(&s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time); } } @@ -271,13 +264,12 @@ markParentDown(HttpTransact::State *s) inline static void markParentUp(HttpTransact::State *s) { - url_mapping *mp = s->url_map.getMapping(); 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, - NHCmd::MARK_UP); - } else if (s->parent_params) { + } else if (nullptr != s->next_hop_strategy) { + s->next_hop_strategy->markNextHop(reinterpret_cast(s->state_machine), s->parent_result.hostname, + s->parent_result.port, NHCmd::MARK_UP); + } else if (nullptr != s->parent_params) { s->parent_params->markParentUp(&s->parent_result); } } @@ -287,12 +279,11 @@ markParentUp(HttpTransact::State *s) inline static bool parentExists(HttpTransact::State *s) { - url_mapping *mp = s->url_map.getMapping(); 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) { + } else if (nullptr != s->next_hop_strategy) { + return s->next_hop_strategy->nextHopExists(reinterpret_cast(s->state_machine)); + } else if (nullptr != s->parent_params) { return s->parent_params->parentExists(&s->request_data); } else { return false; @@ -306,7 +297,6 @@ nextParent(HttpTransact::State *s) { TxnDbg(dbg_ctl_parent_down, "connection to parent %s failed, conn_state: %s, request to origin: %s", s->parent_result.hostname, HttpDebugNames::get_server_state_name(s->current.state), s->request_data.get_host()); - url_mapping *mp = s->url_map.getMapping(); Metrics::Counter::increment(http_rsb.parent_count); if (s->response_action.handled) { s->parent_result.hostname = s->response_action.action.hostname; @@ -319,10 +309,10 @@ nextParent(HttpTransact::State *s) } else { s->parent_result.result = ParentResultType::FAIL; } - } else if (mp && mp->strategy) { + } else if (nullptr != s->next_hop_strategy) { // NextHop only has a findNextHop() function. - mp->strategy->findNextHop(reinterpret_cast(s->state_machine)); - } else if (s->parent_params) { + s->next_hop_strategy->findNextHop(reinterpret_cast(s->state_machine)); + } else if (nullptr != s->parent_params) { s->parent_params->nextParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time); } @@ -404,12 +394,11 @@ response_is_retryable(HttpTransact::State *s, HTTPStatus response_code) if (s->response_action.handled) { return s->response_action.action.responseIsRetryable ? ParentRetry_t::SIMPLE : ParentRetry_t::NONE; } - const url_mapping *mp = s->url_map.getMapping(); - if (mp && mp->strategy) { - return mp->strategy->responseIsRetryable(s->state_machine->sm_id, s->current, response_code); + if (nullptr != s->next_hop_strategy) { + return s->next_hop_strategy->responseIsRetryable(s->state_machine->sm_id, s->current, response_code); } - if (s->parent_params && !s->parent_result.response_is_retryable(s->parent_result.retry_type(), response_code)) { + if (nullptr != s->parent_params && !s->parent_result.response_is_retryable(s->parent_result.retry_type(), response_code)) { return ParentRetry_t::NONE; } const ParentRetry_t s_retry_type = retry_type(s); @@ -6513,8 +6502,8 @@ HttpTransact::process_quick_http_filter(State *s, int method) } // if the "ip_allow" named filter is deactivated in the remap.config, then don't modify anything - url_mapping *mp = s->url_map.getMapping(); - if (mp && !mp->ip_allow_check_enabled_p) { + url_mapping const *const mp = s->url_map.getMapping(); + if (nullptr != mp && !mp->ip_allow_check_enabled_p) { return; } diff --git a/src/proxy/http/remap/NextHopStrategyFactory.cc b/src/proxy/http/remap/NextHopStrategyFactory.cc index 12bad353f69..b0e6ba97f80 100644 --- a/src/proxy/http/remap/NextHopStrategyFactory.cc +++ b/src/proxy/http/remap/NextHopStrategyFactory.cc @@ -124,16 +124,16 @@ NextHopStrategyFactory::NextHopStrategyFactory(const char *file) : fn(file) NextHopStrategyFactory::~NextHopStrategyFactory() { NH_Dbg(NH_DBG_CTL, "destroying NextHopStrategyFactory"); + + for (auto &[_, ptr] : _strategies) { + delete ptr; + } } void NextHopStrategyFactory::createStrategy(const std::string &name, const NHPolicyType policy_type, ts::Yaml::Map &node) { - std::shared_ptr strat; - std::shared_ptr strat_rr; - std::shared_ptr strat_chash; - - strat = strategyInstance(name.c_str()); + NextHopSelectionStrategy const *strat = strategyInstance(name.c_str()); if (strat != nullptr) { NH_Note("A strategy named '%s' has already been loaded and another will not be created.", name.data()); node.bad(); @@ -145,26 +145,28 @@ NextHopStrategyFactory::createStrategy(const std::string &name, const NHPolicyTy case NHPolicyType::FIRST_LIVE: case NHPolicyType::RR_STRICT: case NHPolicyType::RR_IP: - case NHPolicyType::RR_LATCHED: - strat_rr = std::make_shared(name, policy_type, node); + case NHPolicyType::RR_LATCHED: { + NextHopRoundRobin *const strat_rr = new NextHopRoundRobin(name, policy_type, node); _strategies.emplace(std::make_pair(std::string(name), strat_rr)); break; - case NHPolicyType::CONSISTENT_HASH: - strat_chash = std::make_shared(name, policy_type, node); + } + case NHPolicyType::CONSISTENT_HASH: { + NextHopConsistentHash *const strat_chash = new NextHopConsistentHash(name, policy_type, node); _strategies.emplace(std::make_pair(std::string(name), strat_chash)); break; + } default: // handles ParentRR_t::UNDEFINED, no strategy is added break; }; } catch (std::exception &ex) { - strat.reset(); + ; } } -std::shared_ptr -NextHopStrategyFactory::strategyInstance(const char *name) +NextHopSelectionStrategy * +NextHopStrategyFactory::strategyInstance(const char *name) const { - std::shared_ptr ps_strategy; + NextHopSelectionStrategy *ps_strategy = nullptr; if (!strategies_loaded) { NH_Error("no strategy configurations were defined, see definitions in '%s' file", fn.c_str()); diff --git a/src/proxy/http/remap/RemapProcessor.cc b/src/proxy/http/remap/RemapProcessor.cc index a345507ffc3..e14f8c12178 100644 --- a/src/proxy/http/remap/RemapProcessor.cc +++ b/src/proxy/http/remap/RemapProcessor.cc @@ -166,14 +166,11 @@ RemapProcessor::finish_remap(HttpTransact::State *s, UrlRewrite *table) referer_info *ri; map = s->url_map.getMapping(); - if (!map) { + if (nullptr == map) { + Dbg(dbg_ctl_url_rewrite, "Could not find corresponding url_mapping for this transaction"); return false; } - // if there is a configured next hop strategy, make it available in the state. - if (map->strategy) { - s->next_hop_strategy = map->strategy; - } // Do fast ACL filtering (it is safe to check map here) table->PerformACLFiltering(s, map); @@ -310,7 +307,7 @@ RemapProcessor::perform_remap(Continuation *cont, HttpTransact::State *s) url_mapping *map = s->url_map.getMapping(); host_hdr_info *hh_info = &(s->hh_info); - if (!map) { + if (nullptr == map) { Error("Could not find corresponding url_mapping for this transaction %p", s); Dbg(dbg_ctl_url_rewrite, "Could not find corresponding url_mapping for this transaction"); ink_assert(!"this should never happen -- call setup_for_remap first"); @@ -318,6 +315,12 @@ RemapProcessor::perform_remap(Continuation *cont, HttpTransact::State *s) return ACTION_RESULT_DONE; } + // if there is a configured next hop strategy, make it available in the state. + if (nullptr != map->strategy) { + Dbg(dbg_ctl_url_rewrite, "Setting next-hop strategy to %s", map->strategy->strategy_name.c_str()); + s->next_hop_strategy = map->strategy; + } + RemapPlugins plugins(s, request_url, request_header, hh_info); while (!plugins.run_single_remap()) { diff --git a/src/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc b/src/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc index 1c586dc4e51..5e582b9306a 100644 --- a/src/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc +++ b/src/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc @@ -49,9 +49,8 @@ SCENARIO("Testing NextHopConsistentHash class, using policy 'consistent_hash'", GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.") { // load the configuration strtegies. - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); - strategy = nhf.strategyInstance("consistent-hash-1"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("consistent-hash-1"); WHEN("the config is loaded.") { @@ -188,9 +187,8 @@ SCENARIO("Testing NextHopConsistentHash class (all firstcalls), using policy 'co GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.") { - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); - strategy = nhf.strategyInstance("consistent-hash-1"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("consistent-hash-1"); WHEN("the config is loaded.") { @@ -298,9 +296,8 @@ SCENARIO("Testing NextHop ignore_self_detect false", "[NextHopConsistentHash]") GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.") { // load the configuration strtegies. - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); - strategy = nhf.strategyInstance("ignore-self-detect-false"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("ignore-self-detect-false"); HostStatus &hs = HostStatus::instance(); hs.setHostStatus("localhost", TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); @@ -348,9 +345,8 @@ SCENARIO("Testing NextHop ignore_self_detect true", "[NextHopConsistentHash]") GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.") { // load the configuration strtegies. - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); - strategy = nhf.strategyInstance("ignore-self-detect-true"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("ignore-self-detect-true"); HostStatus &hs = HostStatus::instance(); hs.setHostStatus("localhost", TSHostStatus::TS_HOST_STATUS_DOWN, 0, Reason::SELF_DETECT); @@ -398,9 +394,8 @@ SCENARIO("Testing NextHopConsistentHash same host different port markdown", "[Ne GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.") { // load the configuration strtegies. - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); - strategy = nhf.strategyInstance("same-host-different-port"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("same-host-different-port"); WHEN("the config is loaded.") { @@ -466,9 +461,8 @@ SCENARIO("Testing NextHopConsistentHash hash_string override", "[NextHopConsiste GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.") { // load the configuration strtegies. - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); - strategy = nhf.strategyInstance("hash-string-override"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("hash-string-override"); WHEN("the config is loaded.") { @@ -526,9 +520,8 @@ SCENARIO("Testing NextHopConsistentHash class (alternating rings), using policy GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.") { - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); - strategy = nhf.strategyInstance("consistent-hash-2"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/consistent-hash-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("consistent-hash-2"); WHEN("the config is loaded.") { @@ -645,9 +638,8 @@ SCENARIO("Testing NextHopConsistentHash using a peering ring_mode.") GIVEN("Loading the peering.yaml config for 'consistent_hash' tests.") { - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/peering.yaml"); - strategy = nhf.strategyInstance("peering-group-1"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/peering.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("peering-group-1"); WHEN("the config is loaded.") { diff --git a/src/proxy/http/remap/unit-tests/test_NextHopRoundRobin.cc b/src/proxy/http/remap/unit-tests/test_NextHopRoundRobin.cc index 2102902f12c..2fc18755015 100644 --- a/src/proxy/http/remap/unit-tests/test_NextHopRoundRobin.cc +++ b/src/proxy/http/remap/unit-tests/test_NextHopRoundRobin.cc @@ -45,9 +45,8 @@ SCENARIO("Testing NextHopRoundRobin class, using policy 'rr-strict'", "[NextHopR GIVEN("Loading the round-robin-tests.yaml config for round robin 'rr-strict' tests.") { - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/round-robin-tests.yaml"); - strategy = nhf.strategyInstance("rr-strict-exhaust-ring"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/round-robin-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("rr-strict-exhaust-ring"); WHEN("the config is loaded.") { @@ -170,9 +169,8 @@ SCENARIO("Testing NextHopRoundRobin class, using policy 'first-live'", "[NextHop GIVEN("Loading the round-robin-tests.yaml config for round robin 'first-live' tests.") { - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/round-robin-tests.yaml"); - strategy = nhf.strategyInstance("first-live"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/round-robin-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("first-live"); WHEN("the config is loaded.") { @@ -239,13 +237,12 @@ SCENARIO("Testing NextHopRoundRobin class, using policy 'rr-ip'", "[NextHopRound GIVEN("Loading the round-robin-tests.yaml config for round robin 'rr-ip' tests.") { - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/round-robin-tests.yaml"); - strategy = nhf.strategyInstance("rr-ip"); - sockaddr_in sa1 = {}; - sockaddr_in sa2 = {}; - sa1.sin_port = 10000; - sa1.sin_family = AF_INET; + NextHopStrategyFactory nhf(TS_SRC_DIR "/round-robin-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("rr-ip"); + sockaddr_in sa1 = {}; + sockaddr_in sa2 = {}; + sa1.sin_port = 10000; + sa1.sin_family = AF_INET; REQUIRE(inet_pton(AF_INET, "192.168.1.1", &(sa1.sin_addr)) == 1); sa2.sin_port = 10001; sa2.sin_family = AF_INET; @@ -327,9 +324,8 @@ SCENARIO("Testing NextHopRoundRobin class, using policy 'latched'", "[NextHopRou GIVEN("Loading the round-robin-tests.yaml config for round robin 'latched' tests.") { - std::shared_ptr strategy; - NextHopStrategyFactory nhf(TS_SRC_DIR "/round-robin-tests.yaml"); - strategy = nhf.strategyInstance("latched"); + NextHopStrategyFactory nhf(TS_SRC_DIR "/round-robin-tests.yaml"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("latched"); WHEN("the config is loaded.") { diff --git a/src/proxy/http/remap/unit-tests/test_NextHopStrategyFactory.cc b/src/proxy/http/remap/unit-tests/test_NextHopStrategyFactory.cc index 490e389ccb8..9ad20006a5b 100644 --- a/src/proxy/http/remap/unit-tests/test_NextHopStrategyFactory.cc +++ b/src/proxy/http/remap/unit-tests/test_NextHopStrategyFactory.cc @@ -62,7 +62,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("Expect that these results for 'strategy-1'") { - std::shared_ptr strategy = nhf.strategyInstance("strategy-1"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("strategy-1"); REQUIRE(strategy != nullptr); CHECK(strategy->parent_is_proxy == true); CHECK(strategy->max_simple_retries == 1); @@ -70,7 +70,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") // down cast here using the stored pointer so that I can verify the hash_key was set // properly. - NextHopConsistentHash *ptr = static_cast(strategy.get()); + NextHopConsistentHash *const ptr = static_cast(strategy); REQUIRE(ptr != nullptr); CHECK(ptr->hash_key == NHHashKeyType::CACHE_HASH_KEY); @@ -147,7 +147,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("Expect that these results for 'strategy-2'") { - std::shared_ptr strategy = nhf.strategyInstance("strategy-2"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("strategy-2"); REQUIRE(strategy != nullptr); CHECK(strategy->policy_type == NHPolicyType::RR_STRICT); CHECK(strategy->go_direct == true); @@ -234,7 +234,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("Expect that these results for 'strategy-3'") { - std::shared_ptr strategy = nhf.strategyInstance("strategy-3"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("strategy-3"); REQUIRE(strategy != nullptr); CHECK(strategy->policy_type == NHPolicyType::RR_IP); CHECK(strategy->go_direct == true); @@ -309,7 +309,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("Expect that these results for 'strategy-4'") { - std::shared_ptr strategy = nhf.strategyInstance("strategy-4"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("strategy-4"); REQUIRE(strategy != nullptr); CHECK(strategy->policy_type == NHPolicyType::RR_LATCHED); CHECK(strategy->go_direct == true); @@ -375,7 +375,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("expect the following details.") { - std::shared_ptr strategy = nhf.strategyInstance("mid-tier-north"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("mid-tier-north"); REQUIRE(strategy != nullptr); CHECK(strategy->parent_is_proxy == false); CHECK(strategy->max_simple_retries == 2); @@ -454,7 +454,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("expect the following results.") { - std::shared_ptr strategy = nhf.strategyInstance("mid-tier-south"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("mid-tier-south"); REQUIRE(strategy != nullptr); CHECK(strategy->policy_type == NHPolicyType::RR_LATCHED); CHECK(strategy->parent_is_proxy == false); @@ -534,7 +534,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("expect the following results.") { - std::shared_ptr strategy = nhf.strategyInstance("mid-tier-east"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("mid-tier-east"); REQUIRE(strategy != nullptr); CHECK(strategy->policy_type == NHPolicyType::FIRST_LIVE); CHECK(strategy->parent_is_proxy == false); @@ -614,7 +614,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("expect the following results.") { - std::shared_ptr strategy = nhf.strategyInstance("mid-tier-west"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("mid-tier-west"); REQUIRE(strategy != nullptr); CHECK(strategy->policy_type == NHPolicyType::RR_STRICT); CHECK(strategy->go_direct == true); @@ -693,7 +693,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") { THEN("expect the following results.") { - std::shared_ptr strategy = nhf.strategyInstance("mid-tier-midwest"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("mid-tier-midwest"); REQUIRE(strategy != nullptr); CHECK(strategy->policy_type == NHPolicyType::CONSISTENT_HASH); CHECK(strategy->parent_is_proxy == false); @@ -701,7 +701,7 @@ SCENARIO("factory tests loading yaml configs", "[loadConfig]") // I need to down cast here using the stored pointer so that I can verify that // the hash_key was set properly. - NextHopConsistentHash *ptr = static_cast(strategy.get()); + NextHopConsistentHash *const ptr = static_cast(strategy); REQUIRE(ptr != nullptr); CHECK(ptr->hash_key == NHHashKeyType::URL_HASH_KEY); @@ -797,7 +797,7 @@ SCENARIO("factory tests loading yaml configs from a directory", "[loadConfig]") { THEN("expect the following results.") { - std::shared_ptr strategy = nhf.strategyInstance("mid-tier-north"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("mid-tier-north"); REQUIRE(strategy != nullptr); CHECK(strategy->parent_is_proxy == false); CHECK(strategy->max_simple_retries == 2); @@ -879,7 +879,7 @@ SCENARIO("factory tests loading yaml configs from a directory", "[loadConfig]") { THEN("expect the following results.") { - std::shared_ptr strategy = nhf.strategyInstance("mid-tier-south"); + NextHopSelectionStrategy *const strategy = nhf.strategyInstance("mid-tier-south"); REQUIRE(strategy != nullptr); CHECK(strategy->policy_type == NHPolicyType::RR_LATCHED); CHECK(strategy->parent_is_proxy == false); diff --git a/tests/gold_tests/pluginTest/strategies/strategies_plugins.test.py b/tests/gold_tests/pluginTest/strategies/strategies_plugins.test.py new file mode 100644 index 00000000000..2dda1262b27 --- /dev/null +++ b/tests/gold_tests/pluginTest/strategies/strategies_plugins.test.py @@ -0,0 +1,297 @@ +''' +''' +# 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. +import os + +Test.Summary = ''' +Combined header_rewrite/regex_remap/tslua strategies tests +''' + +# Test description: +# Preload the cache with the entire asset to be range requested. +# Reload remap rule with slice plugin +# Request content through the slice plugin + +Test.SkipUnless( + Condition.PluginExists('header_rewrite.so'), + Condition.PluginExists('regex_remap.so'), + Condition.PluginExists('tslua.so'), +) +Test.ContinueOnFail = False + +dns = Test.MakeDNServer("dns") + +origins = [] +num_origins = 3 +for ind in range(num_origins): + name = f"nh{ind}" + origin = Test.MakeOriginServer(name, options={"--verbose": ""}) + request_header = { + "headers": f"GET / HTTP/1.1\r\nHost: origin\r\n\r\n", + "timestamp": "1469733493.993", + "body": "", + } + response_header = { + "headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", + "timestamp": "1469733493.993", + "body": "", + } + origin.addResponse("sessionfile.log", request_header, response_header) + request_header = { + "headers": "GET /path HTTP/1.1\r\nHost: origin\r\n\r\n", + "timestamp": "1469733493.993", + "body": "", + } + response_header = { + "headers": f"HTTP/1.1 200 OK\r\nConnection: close\r\nOrigin: {name}\r\n\r\n", + "timestamp": "1469733493.993", + "body": name, + } + origin.addResponse("sessionfile.log", request_header, response_header) + request_header = { + "headers": f"GET /path/{name} HTTP/1.1\r\nHost: origin\r\n\r\n", + "timestamp": "1469733493.993", + "body": "", + } + response_header = { + "headers": f"HTTP/1.1 200 OK\r\nConnection: close\r\nOrigin: {name}\r\n\r\n", + "timestamp": "1469733493.993", + "body": name, + } + origin.addResponse("sessionfile.log", request_header, response_header) + origin.ReturnCode = 0 + origins.append(origin) + dns.addRecords(records={name: ["127.0.0.1"]}) + +# Define ATS and configure +ts = Test.MakeATSProcess("ts", enable_cache=False) +ts.ReturnCode = 0 +ts.Disk.records_config.update( + { + 'proxy.config.dns.nameservers': f"127.0.0.1:{dns.Variables.Port}", + 'proxy.config.dns.resolv_conf': "NULL", + 'proxy.config.http.cache.http': 0, + "proxy.config.http.insert_response_via_str": 1, + 'proxy.config.http.uncacheable_requests_bypass_parent': 0, + 'proxy.config.http.no_dns_just_forward_to_parent': 1, + 'proxy.config.http.parent_proxy.mark_down_hostdb': 0, + 'proxy.config.http.parent_proxy.self_detect': 0, + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': "url_rewrite|next_hop|dns|parent|regex_remap|header_rewrite|tslua|http|hostdb", + }) + +ts.Disk.MakeConfigFile("hdr_rw.config").AddLines( + [ + "cond %{REMAP_PSEUDO_HOOK}", + 'cond %{CLIENT-HEADER:Strategy} ="" [NOT]', + "set-next-hop-strategy %{CLIENT-HEADER:Strategy}", + ]) +ts.Disk.MakeConfigFile("regex_remap.config").AddLines( + [ + "/nh0 http://origin/path @strategy=nh1", + '/nh1 http://origin/path @strategy=', + "/nh2 http://origin/path @strategy=nh0", + ]) +ts.Disk.MakeConfigFile("strategies.lua").AddLines( + [ + 'function do_remap()', + ' local uri = ts.client_request.get_uri()', + ' if uri:find("nh0") then', + ' ts.http.set_next_hop_strategy("nh1")', + ' elseif uri:find("nh1") then', + ' ts.http.set_next_hop_strategy("")', + ' elseif uri:find("nh2") then', + ' ts.http.set_next_hop_strategy("nh0")', + ' end', + ' ts.client_request.set_uri("path")', + ' return 0', + 'end', + ]) + +# parent.config +ts.Disk.parent_config.AddLines( + [f'dest_domain=. parent="nh2:{origins[2].Variables.Port}" round_robin=false go_direct=false parent_is_proxy=false']) + +# build strategies.yaml file +ts.Disk.File(ts.Variables.CONFIGDIR + "/strategies.yaml", id="strategies", typename="ats:config") + +s = ts.Disk.strategies +s.AddLine("groups:") +for ind in range(num_origins - 1): + name = f"nh{ind}" + s.AddLines( + [ + f" - &g{ind}", + f" - host: {name}", + f" protocol:", + f" - scheme: http", + f" port: {origins[ind].Variables.Port}", + f" weight: 1.0", + ]) + +s.AddLine("strategies:") + +# third ts_nh +for ind in range(num_origins - 1): + s.AddLines( + [ + f" - strategy: nh{ind}", + f" policy: consistent_hash", + f" hash_key: path", + f" go_direct: false", + f" parent_is_proxy: false", + f" ignore_self_detect: true", + f" groups:", + f" - *g{ind}", + f" scheme: http", + ]) + +ts.Disk.remap_config.AddLines( + [ + "map http://nh0_hr http://origin @strategy=nh0 @plugin=header_rewrite.so @pparam=hdr_rw.config", + "map http://nh1_hr http://origin @strategy=nh1 @plugin=header_rewrite.so @pparam=hdr_rw.config", + "map http://nh2_hr http://origin @plugin=header_rewrite.so @pparam=hdr_rw.config", + "map http://nh0_rr http://origin @strategy=nh0 @plugin=regex_remap.so @pparam=regex_remap.config", + "map http://nh1_rr http://origin @strategy=nh1 @plugin=regex_remap.so @pparam=regex_remap.config", + "map http://nh2_rr http://origin @plugin=regex_remap.so @pparam=regex_remap.config", + "map http://nh0_lua http://origin @strategy=nh0 @plugin=tslua.so @pparam=strategies.lua", + "map http://nh1_lua http://origin @strategy=nh1 @plugin=tslua.so @pparam=strategies.lua", + "map http://nh2_lua http://origin @plugin=tslua.so @pparam=strategies.lua", + ]) + +# Tests + +# stdout for body, stderr for headers +curl_and_args = '-s -o /dev/stdout -D /dev/stderr -x localhost:{}'.format(ts.Variables.port) + +# header rewrite + +# 0 - nh0 default request +tr = Test.AddTestRun("nh0_hr straight through request") +ps = tr.Processes.Default +for ind in range(num_origins): + origin = origins[ind] + ps.StartBefore(origin, ready=When.PortOpen(origin.Variables.Port)) + tr.StillRunningAfter = origin +ps.StartBefore(dns) +ps.StartBefore(Test.Processes.ts) +tr.MakeCurlCommand(curl_and_args + " http://nh0_hr/path", ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh0", "expected nh0") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# 1 - nh1_hr default request +tr = Test.AddTestRun("nh1_hr straight through request") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + " http://nh1_hr/path", ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh1", "expected nh1") +tr.StillRunningAfter = ts + +# 2 - nh2_hr default request +tr = Test.AddTestRun("nh2_hr straight through request") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + " http://nh2_hr/path", ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh2", "expected nh2") +tr.StillRunningAfter = ts + +# 3 switch strategies +tr = Test.AddTestRun("nh0_hr switch to nh1") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh0_hr/path -H "Strategy: nh1"', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh1", "expected nh1") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# 4 strategy to parent.config +tr = Test.AddTestRun("nh1_hr switch to parent.config") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh1_hr/path -H "Strategy: null"', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh2", "expected nh2") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# 5 parent.config strategy to strategy +tr = Test.AddTestRun("nh2_hr switch to nh0") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh2_hr/path -H "Strategy: nh0"', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh0", "expected nh0") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# regex_remap + +# 6 switch strategies +tr = Test.AddTestRun("nh0_rr switch to nh1") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh0_rr/nh0', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh1", "expected nh1") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# ' strategy to parent.config +tr = Test.AddTestRun("nh1_rr switch to parent.config") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh1_rr/nh1', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh2", "expected nh2") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# 8 parent.config strategy to strategy +tr = Test.AddTestRun("nh2_rr switch to nh0") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh2_rr/nh2', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh0", "expected nh0") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# tslua + +# 9 switch strategies +tr = Test.AddTestRun("nh0_lua switch to nh1") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh0_lua/nh0', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh1", "expected nh1") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# 10 strategy to parent.config +tr = Test.AddTestRun("nh1_lua switch to parent.config") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh1_lua/nh1', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh2", "expected nh2") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns + +# 11 parent.config strategy to strategy +tr = Test.AddTestRun("nh2_lua switch to nh0") +ps = tr.Processes.Default +tr.MakeCurlCommand(curl_and_args + ' http://nh2_lua/nh2', ts=ts) +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.ContainsExpression("nh0", "expected nh0") +tr.StillRunningAfter = ts +tr.StillRunnerAfter = dns