This is an automated email from the ASF dual-hosted git repository.
bnolsen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 9534d955aa API for Next Hop Strategy rebind during a transaction
(#12512)
9534d955aa is described below
commit 9534d955aa78657d311c7170aae394c561d6bcc6
Author: Brian Olsen <[email protected]>
AuthorDate: Mon Oct 20 16:18:53 2025 -0600
API for Next Hop Strategy rebind during a transaction (#12512)
* Create ts API functions to change a transaction strategy.
This adds 'c' friendly api calls to get and set strategies,
to get the name of a given strategy and
to get a strategy from the strategy factory by name.
Also includes additions to header_rewrite, regex_remap and lua plugins.
* in plugins allow 'null' name to clear txn strategy
* add an inkapi test for strategy get and set
* add test for setting bad strategy
---------
Co-authored-by: Brian Olsen <[email protected]>
---
doc/admin-guide/plugins/header_rewrite.en.rst | 13 +
doc/admin-guide/plugins/lua.en.rst | 44 +++
doc/admin-guide/plugins/regex_remap.en.rst | 1 +
.../functions/TSHttpNextHopStrategyNameGet.en.rst | 49 +++
.../TSHttpTxnNextHopNamedStrategyGet.en.rst | 52 ++++
.../functions/TSHttpTxnNextHopStrategyGet.en.rst | 49 +++
.../functions/TSHttpTxnNextHopStrategySet.en.rst | 56 ++++
include/proxy/http/HttpTransact.h | 10 +-
include/proxy/http/remap/NextHopStrategyFactory.h | 7 +-
include/proxy/http/remap/UrlMapping.h | 6 +-
include/ts/ts.h | 61 ++++
plugins/header_rewrite/conditions.cc | 9 +
plugins/header_rewrite/factory.cc | 2 +
plugins/header_rewrite/operators.cc | 47 +++
plugins/header_rewrite/operators.h | 19 ++
plugins/header_rewrite/statement.cc | 2 +
plugins/header_rewrite/statement.h | 1 +
plugins/lua/ts_lua_http.cc | 66 +++++
plugins/regex_remap/regex_remap.cc | 22 ++
src/api/InkAPI.cc | 53 ++++
src/api/InkAPITest.cc | 32 +-
src/proxy/http/HttpSM.cc | 5 +-
src/proxy/http/HttpTransact.cc | 73 ++---
src/proxy/http/remap/NextHopStrategyFactory.cc | 28 +-
src/proxy/http/remap/RemapProcessor.cc | 15 +-
.../remap/unit-tests/test_NextHopConsistentHash.cc | 40 +--
.../remap/unit-tests/test_NextHopRoundRobin.cc | 28 +-
.../unit-tests/test_NextHopStrategyFactory.cc | 26 +-
.../strategies/strategies_plugins.test.py | 330 +++++++++++++++++++++
29 files changed, 1019 insertions(+), 127 deletions(-)
diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst
b/doc/admin-guide/plugins/header_rewrite.en.rst
index 1711337964..e7f8c74769 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 ``<part>`` 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 ``<value>`` 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 <name>
+
+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 d065d3d1e3..aba1ff7db3 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 547309faca..b309ca0726 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 0000000000..273b636fb8
--- /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 <ts/ts.h>
+
+.. 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 0000000000..949d92346d
--- /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 <ts/ts.h>
+
+.. 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 0000000000..c609511bbe
--- /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 <ts/ts.h>
+
+.. 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 0000000000..33ca4b6266
--- /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 <ts/ts.h>
+
+.. 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 62a44da904..01bcb8182e 100644
--- a/include/proxy/http/HttpTransact.h
+++ b/include/proxy/http/HttpTransact.h
@@ -723,11 +723,11 @@ public:
// able to defer some work in building the request
TransactFunc_t pending_work = nullptr;
- HttpRequestData request_data;
- ParentConfigParams *parent_params = nullptr;
- std::shared_ptr<NextHopSelectionStrategy> 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 4822460d91..aae01e3d1b 100644
--- a/include/proxy/http/remap/NextHopStrategyFactory.h
+++ b/include/proxy/http/remap/NextHopStrategyFactory.h
@@ -45,7 +45,10 @@ public:
NextHopStrategyFactory() = delete;
NextHopStrategyFactory(const char *file);
~NextHopStrategyFactory();
- std::shared_ptr<NextHopSelectionStrategy> 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 @@ private:
std::string fn;
void loadConfigFile(const std::string &file, std::stringstream &doc,
std::unordered_set<std::string> &include_once);
void createStrategy(const std::string &name, const NHPolicyType
policy_type, ts::Yaml::Map &node);
- std::unordered_map<std::string, std::shared_ptr<NextHopSelectionStrategy>>
_strategies;
+ std::unordered_map<std::string, NextHopSelectionStrategy *> _strategies;
};
diff --git a/include/proxy/http/remap/UrlMapping.h
b/include/proxy/http/remap/UrlMapping.h
index a35b66f8c1..dabab07118 100644
--- a/include/proxy/http/remap/UrlMapping.h
+++ b/include/proxy/http/remap/UrlMapping.h
@@ -112,9 +112,9 @@ public:
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<NextHopSelectionStrategy> strategy = nullptr;
- std::string remapKey;
- std::atomic<uint64_t> _hitCount = 0; // counter can
overflow
+ NextHopSelectionStrategy *strategy = nullptr;
+ std::string remapKey;
+ std::atomic<uint64_t> _hitCount = 0; // counter can overflow
int
getRank() const
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 7bfab14982..8ea727082f 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1579,6 +1579,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 dcb44831ed..11357033e2 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 3317b49b33..9e0499f060 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 4ac0e245b0..e1a9683d3c 100644
--- a/plugins/header_rewrite/operators.cc
+++ b/plugins/header_rewrite/operators.cc
@@ -1627,3 +1627,50 @@ 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());
+ } 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 4ff4f0edcb..a0b825d3d0 100644
--- a/plugins/header_rewrite/operators.h
+++ b/plugins/header_rewrite/operators.h
@@ -651,3 +651,22 @@ protected:
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 ca840d7a9b..57eab9d62f 100644
--- a/plugins/header_rewrite/statement.cc
+++ b/plugins/header_rewrite/statement.cc
@@ -118,6 +118,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 28e7195128..aac03d878a 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 b631206bb2..b1bc7c104a 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 2c9e5db29a..be57f75998 100644
--- a/plugins/regex_remap/regex_remap.cc
+++ b/plugins/regex_remap/regex_remap.cc
@@ -226,6 +226,11 @@ public:
{
return _lowercase_substitutions;
}
+ inline std::string const &
+ strategy() const
+ {
+ return _strategy;
+ }
// Hold an overridable configurations
struct Override {
@@ -263,6 +268,8 @@ private:
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 6c6f34b37e..f1869503f0 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -4991,6 +4991,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<HttpSM const *>(txnp);
+
+ return static_cast<void *>(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<HttpSM *>(txnp);
+ auto strategy = reinterpret_cast<NextHopSelectionStrategy const *>(stratptr);
+
+ sm->t_state.next_hop_strategy = const_cast<NextHopSelectionStrategy
*>(strategy);
+}
+
+char const *
+TSHttpNextHopStrategyNameGet(void const *stratptr)
+{
+ char const *name = nullptr;
+ if (nullptr != stratptr) {
+ auto strategy = reinterpret_cast<NextHopSelectionStrategy const
*>(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<HttpSM const *>(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<void const *>(strat);
+}
+
TSReturnCode
TSHttpTxnParentProxyGet(TSHttpTxn txnp, const char **hostname, int *port)
{
diff --git a/src/api/InkAPITest.cc b/src/api/InkAPITest.cc
index a1dc169a74..521474bedd 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<TSHttpTxn>(data);
+
+ // this is an invalid pointer but the contents don't matter for this test.
+ void const *const exp = reinterpret_cast<void *>(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<TSHttpTxn>(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<TSHttpTxn>(data), (void *)0x01);
+
checkHttpTxnClientReqGet(test, data);
TSHttpTxnReenable(static_cast<TSHttpTxn>(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<TSHttpTxn>(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 2978adfbb2..95d677a390 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 df4727cd46..e8d53be3f3 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<TSHttpTxn>(s->state_machine));
- } else if (s->parent_params) {
+ } else if (nullptr != s->next_hop_strategy) {
+
s->next_hop_strategy->findNextHop(reinterpret_cast<TSHttpTxn>(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<TSHttpTxn>(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<TSHttpTxn>(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<TSHttpTxn>(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<TSHttpTxn>(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<TSHttpTxn>(s->state_machine));
- } else if (s->parent_params) {
+ } else if (nullptr != s->next_hop_strategy) {
+ return
s->next_hop_strategy->nextHopExists(reinterpret_cast<TSHttpTxn>(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<TSHttpTxn>(s->state_machine));
- } else if (s->parent_params) {
+
s->next_hop_strategy->findNextHop(reinterpret_cast<TSHttpTxn>(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);
@@ -6516,8 +6505,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 12bad353f6..b0e6ba97f8 100644
--- a/src/proxy/http/remap/NextHopStrategyFactory.cc
+++ b/src/proxy/http/remap/NextHopStrategyFactory.cc
@@ -124,16 +124,16 @@ done:
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<NextHopSelectionStrategy> strat;
- std::shared_ptr<NextHopRoundRobin> strat_rr;
- std::shared_ptr<NextHopConsistentHash> 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<NextHopRoundRobin>(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<NextHopConsistentHash>(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<NextHopSelectionStrategy>
-NextHopStrategyFactory::strategyInstance(const char *name)
+NextHopSelectionStrategy *
+NextHopStrategyFactory::strategyInstance(const char *name) const
{
- std::shared_ptr<NextHopSelectionStrategy> 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 a345507ffc..e14f8c1217 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 1c586dc4e5..5e582b9306 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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 2102902f12..2fc1875501 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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 490e389ccb..9ad20006a5 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<NextHopSelectionStrategy> 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<NextHopConsistentHash
*>(strategy.get());
+ NextHopConsistentHash *const ptr = static_cast<NextHopConsistentHash
*>(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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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<NextHopConsistentHash
*>(strategy.get());
+ NextHopConsistentHash *const ptr = static_cast<NextHopConsistentHash
*>(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<NextHopSelectionStrategy> 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<NextHopSelectionStrategy> 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 0000000000..6a8b1a549b
--- /dev/null
+++ b/tests/gold_tests/pluginTest/strategies/strategies_plugins.test.py
@@ -0,0 +1,330 @@
+'''
+'''
+# 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",
+ "/nemo http://origin/path @strategy=nemo",
+ ])
+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")',
+ ' elseif uri:find("nemo") then',
+ ' ts.http.set_next_hop_strategy("nemo")',
+ ' 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
+
+# 6 try to switch to non existent strategy
+tr = Test.AddTestRun("nh0_hr switch to nemo (fail)")
+ps = tr.Processes.Default
+tr.MakeCurlCommand(curl_and_args + ' http://nh0_hr/path -H "Strategy: nemo"',
ts=ts)
+ps.ReturnCode = 0
+ps.Streams.stdout.Content = Testers.ContainsExpression("nh0", "expected nh0")
+tr.StillRunningAfter = ts
+tr.StillRunnerAfter = dns
+
+# regex_remap
+
+# 7 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
+
+# 8 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
+
+# 9 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
+
+# 10 switch strategies (fail)
+tr = Test.AddTestRun("nh0_rr switch to nemo")
+ps = tr.Processes.Default
+tr.MakeCurlCommand(curl_and_args + ' http://nh0_rr/nemo', ts=ts)
+ps.ReturnCode = 0
+ps.Streams.stdout.Content = Testers.ContainsExpression("nh0", "expected nh0")
+tr.StillRunningAfter = ts
+tr.StillRunnerAfter = dns
+
+# tslua
+
+# 11 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
+
+# 12 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
+
+# 13 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
+
+# 14 switch strategies, fail
+tr = Test.AddTestRun("nh0_lua switch to nemo")
+ps = tr.Processes.Default
+tr.MakeCurlCommand(curl_and_args + ' http://nh0_lua/nemo', ts=ts)
+ps.ReturnCode = 0
+ps.Streams.stdout.Content = Testers.ContainsExpression("nh0", "expected nh0")
+tr.StillRunningAfter = ts
+tr.StillRunnerAfter = dns
+
+# Overriding the built in ERROR check since we expect some ERROR messages
+ts.Disk.diags_log.Content = Testers.ContainsExpression("ERROR", "Some tests
are failure tests")