You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2020/01/23 22:02:52 UTC

[trafficserver] 01/12: Add a remap @strategy tag and nexthop selection strategies to remap.

This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit b0a7b8a6dda24a1694d813d3bdf28b064ac2aa60
Author: John Rushford <jr...@apache.org>
AuthorDate: Fri Aug 2 22:59:04 2019 +0000

    Add a remap @strategy tag and nexthop selection strategies to remap.
    
    (cherry picked from commit 128507ae5a69ab0cbaa6d9707cfe2ad1d95cbed8)
    
     Conflicts:
    	proxy/http/remap/UrlRewrite.cc
---
 .gitignore                                         |   5 +
 configs/strategies.yaml.default                    | 132 +++
 doc/admin-guide/files/strategies.yaml.en.rst       | 231 +++++
 mgmt/RecordsConfig.cc                              |   2 +
 proxy/ParentSelection.cc                           |   2 -
 proxy/ParentSelection.h                            |  25 +-
 proxy/http/HttpTransact.cc                         | 238 ++++-
 proxy/http/HttpTransact.h                          |   3 +-
 proxy/http/remap/Makefile.am                       |  99 ++-
 proxy/http/remap/NextHopConsistentHash.cc          | 402 +++++++++
 proxy/http/remap/NextHopConsistentHash.h           |  54 ++
 proxy/http/remap/NextHopRoundRobin.cc              | 219 +++++
 proxy/http/remap/NextHopRoundRobin.h               |  45 +
 proxy/http/remap/NextHopSelectionStrategy.cc       | 377 ++++++++
 proxy/http/remap/NextHopSelectionStrategy.h        | 215 +++++
 proxy/http/remap/NextHopStrategyFactory.cc         | 259 ++++++
 proxy/http/remap/NextHopStrategyFactory.h          |  54 ++
 proxy/http/remap/RemapConfig.cc                    |  27 +
 proxy/http/remap/RemapConfig.h                     |   1 +
 proxy/http/remap/RemapProcessor.cc                 |   5 +
 proxy/http/remap/UrlMapping.h                      |   3 +
 proxy/http/remap/UrlRewrite.cc                     |   6 +
 proxy/http/remap/UrlRewrite.h                      |   2 +
 proxy/http/remap/unit-tests/combined.yaml          | 170 ++++
 .../remap/unit-tests/consistent-hash-tests.yaml    | 171 ++++
 proxy/http/remap/unit-tests/hosts.yaml             |  71 ++
 proxy/http/remap/unit-tests/nexthop_test_stubs.cc  | 144 ++++
 proxy/http/remap/unit-tests/nexthop_test_stubs.h   |  85 ++
 proxy/http/remap/unit-tests/round-robin-tests.yaml | 207 +++++
 proxy/http/remap/unit-tests/simple-strategy.yaml   | 118 +++
 .../remap/unit-tests/strategies-dir/01-hosts.yaml  |  60 ++
 .../remap/unit-tests/strategies-dir/02-groups.yaml |  37 +
 .../unit-tests/strategies-dir/03-strategies.yaml   |  65 ++
 proxy/http/remap/unit-tests/strategy.yaml          |  59 ++
 .../remap/unit-tests/test_NextHopConsistentHash.cc | 385 +++++++++
 .../remap/unit-tests/test_NextHopRoundRobin.cc     | 306 +++++++
 .../unit-tests/test_NextHopStrategyFactory.cc      | 953 +++++++++++++++++++++
 37 files changed, 5198 insertions(+), 39 deletions(-)

diff --git a/.gitignore b/.gitignore
index e5629e9..595f7b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,6 +108,11 @@ proxy/hdrs/test_proxy_hdrs
 proxy/hdrs/test_hdr_heap
 proxy/hdrs/test_Huffmancode
 proxy/hdrs/test_XPACK
+proxy/http/remap/test_NextHopRoundRobin
+proxy/http/remap/test_NextHopStrategyFactory
+proxy/http/remap/test_PluginDso
+proxy/http/remap/test_PluginFactory
+proxy/http/remap/test_RemapPluginInfo
 proxy/http/test_proxy_http
 proxy/http/remap/test_*
 proxy/http2/test_Http2DependencyTree
diff --git a/configs/strategies.yaml.default b/configs/strategies.yaml.default
new file mode 100644
index 0000000..c2f3482
--- /dev/null
+++ b/configs/strategies.yaml.default
@@ -0,0 +1,132 @@
+#
+# strategies.yaml
+#
+# Documentation:
+#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/strategies.yaml.en.html
+#
+# The purpose of this file is to specify the strategies available for
+#   use in locating upstream caches for use to satisfy requests
+#
+# This is a YAML formatted file to define hosts, groups of hosts and next hop strategies that 
+# may be used by remap
+#
+# There are three top-level YAML name spaces: 'hosts', 'groups', and 'strategies'.
+#   'hosts' is a YAML list of host's definitions and is used when defining 'groups' YAML 
+#     references are supported. 
+#   'groups' is a YAML list that aggregates a group of hosts together and serves as the 
+#     equivalent to the rings used in parent.config.  You may define upto five groups in a 
+#     config, see MAX_GROUPS.
+#   'strategies' is a YAML list of strategy definitions. 
+#
+# Files may be broken up into several different files.  The main file loaded by the Next Hop 
+# Strategy factory is this file, strategies.yaml.  You may move the 'hosts' and 'groups' 
+# definitions into separate files and then include them in this file using: 
+#
+#   '#include path_to_hosts_and_groups_file'
+#
+# It is even possible to put individual strategies into separate file.  The Next Hop 
+# strategy factory  concatenates all included files together in a single YAML document at 
+# each point where it sees an '#include file_name'.  When using this feature you just need to
+# ensure that the final concatenation is a valid YAML document with 'hosts', 'groups'
+# and 'strategies' in this given order.
+#
+# 
+# This example YAML document shows a complete definiton in a single strategies.yaml
+# file.  There are other example unit test files in the source tree showing examples of
+# using '#include' and different formats available for use, proxy/http/remap/unit-tests/
+# See the documentation which describes each parameter in detail.
+#
+# Example:
+#
+#
+# hosts:
+#   - &p1 
+#     host: p1.foo.com
+#     hash_string: slsklslsk # optional hash string that replaces the hostname in consistent hashing.
+#     protocol:
+#       - scheme: http
+#         port:  80
+#         health_check_url: http://192.168.1.1:80
+#       - scheme: https
+#         port: 443
+#         health_check_url: https://192.168.1.1:443
+#   - &p2
+#     host: p2.foo.com
+#     protocol:
+#       - scheme: http
+#         port: 80
+#         health_check_url: http://192.168.1.2:80
+#       - scheme: https
+#         port: 443
+#         health_check_url: https://192.168.1.2:443
+#   - &s1 
+#     host: s1.foo.com
+#     hash_string: slsklslsk # optional hash string that replaces the hostname in consistent hashing.
+#     protocol:
+#       - scheme: http
+#         port: 80
+#         health_check_url: http://192.168.2.1:80
+#       - scheme: https 
+#         port: 443
+#         health_check_url: https://192.168.2.1:443
+#   - &s2
+#     host: s2.foo.com
+#     protocol:
+#       - scheme: http
+#         port: 80
+#         health_check_url: http://192.168.2.2:80
+#       - scheme: https
+#         port: 443
+#         health_check_url: https://192.168.2.2:443
+# groups:
+#   - &g1
+#     - <<: *p1
+#       weight: 0.5
+#     - <<: *p2
+#       weight: 0.5
+#   - &g2
+#     - <<: *s1
+#       weight: 2.0
+#     - <<: *s2
+#       weight: 1.0
+# strategies:
+#   - strategy: 'mid-tier-north'
+#     policy: rr_ip # Selection strategy policy: Enum of 'consistent_hash' or 'first_live' or 'rr_strict' or 'rr_ip' or 'latched'
+#     hash_key: hostname # optional key to use for Hashing. Enum of 'url' or 'uri' or 'hostname' or 'path' or 'path+query' or 'cache_key' or 'path+fragment'
+#     go_direct: true # transactions may routed directly to the origin true/false default is true.
+#     parent_is_proxy: false  # next hop hosts  are origin servers when set to 'false', defaults to true and indicates next hop hosts are ats cache's.
+#     groups: # groups of hosts, these groups are used as rings in consistent hash and arrays of host groups for round_robin.
+#       - *g1
+#       - *g2
+#     scheme: http 
+#     failover: 
+#       max_simple_retries: 2 # default is 1, indicates the maximum number of simple retries for the listed response codes.
+#       ring_mode: exhaust_ring # enumerated as exhaust_ring or alternate_ring
+#           #1) in 'exhaust_ring' mode all the servers in a ring are exhausted before failing over to secondary ring
+#           #2) in 'alternate_ring' mode causes the failover to another server in secondary ring.
+#       response_codes: # defines the responses codes for failover in exhaust_ring mode
+#         - 404
+#         - 502
+#         - 503
+#       health_check: # specifies the list of healthchecks that should be considered for failover. A list of enums: 'passive' or 'active'
+#         - passive
+#         - active
+#   - strategy: 'mid-tier-south'
+#     policy: latched
+#     hash_key: uri 
+#     go_direct: false 
+#     parent_is_proxy: false  # next hop hosts  are origin servers
+#     groups:
+#       - *g1
+#       - *g2
+#     scheme: https 
+#     failover: 
+#       max_simple_retries: 2 
+#       ring_mode: alternate_ring
+#       response_codes:
+#         - 404
+#         - 502
+#         - 503
+#       health_check: 
+#         - passive
+#         - active
diff --git a/doc/admin-guide/files/strategies.yaml.en.rst b/doc/admin-guide/files/strategies.yaml.en.rst
new file mode 100644
index 0000000..3a2c52a
--- /dev/null
+++ b/doc/admin-guide/files/strategies.yaml.en.rst
@@ -0,0 +1,231 @@
+.. 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
+
+===============
+strategies.yaml
+===============
+
+.. configfile:: strategies.yaml
+
+The :file:`strategies.yaml` file identifies the next hop proxies used in an
+cache hierarchy and the algorithms used to select the next hop proxy. Use 
+this file to perform the following configuration:
+
+-  Set up next hop cache hierarchies, with multiple parents and parent
+   failover
+
+Traffic Server uses the :file:`strategies.yaml` file only when one or more
+remap lines in remap.config specifies the use of a strategy with the @strategy tag.
+remap.config Example::
+
+  map http://www.foo.com http://www.bar.com @strategy='mid-tier-north'
+
+After you modify the :file:`strategies.yaml` file, run the :option:`traffic_ctl config reload`
+command to apply your changes.
+
+Format
+======
+
+The `strategies.yaml` is a YAML document with three top level namespaces: **hosts**, **groups**
+and **strategies**.  These name spaces may be in separate files.  When in separate files, use
+**#include filename** in the `strategies.yaml` so that they are concatenated by the strategy
+factory into a single YAML document in the order, **hosts**, **groups**, and the **strategies**.
+
+Alternatively if the config parameter `proxy.config.url_remap.strategies.filename` refers to 
+a directory, the NextHopStrategyFactory will alphanumerically concatenate all files in that directory that end in `.yaml` by name into a single document stream for parsing.  The final document must be a vaild `YAML` document with single `strategies` node and optionally a single `hosts` and `groups` node.  Any **#include filename** strings are ignored when reading `.yaml` files in a directory.
+
+Hosts definitions
+=================
+
+The **hosts** definitions is a **YAML** list of hosts.  This list is **optional** but if not used, the
+**groups** list **must** include complete defintions for hosts.  See the **group** examples below.
+
+In the example below, **hosts** is a **YAML** list of hosts.  Each host entry  uses a **YAML** anchor, 
+**&p1** and **&p2** that may be used elsewhere in the **YAML** document to refer to hosts **p1** and **p2**.
+
+- **host**: the host value is a hostname string
+- **protocol**: a list of schemes, ports, and health check urls for  the host.
+- **healthcheck**: health check information with the **url** used to check 
+  the hosts health by some external health check agent.
+  
+Example::
+
+  hosts:
+    - &p1
+      host: p1.foo.com
+      protocol:
+        - scheme: http
+          port: 80
+          health_check_url: http://192.168.1.1:80
+        - scheme: https
+          port: 443
+          health_check_url: https://192.168.1.1:443
+  	- &p2
+      host: p2.foo.com
+      protocol:
+        - scheme: http
+          port: 80
+          health_check_url: http://192.168.1.2:80
+  
+Groups definitions
+==================
+
+The **groups** definitions is a **YAML** list of host groups.  host groups are used as the primary and secondary groups used by nexthop to choose hosts from.  The first group is the **primary** group next hop chooses hosts from.  The remaing groups are used failover.  The **strategies** **policy** specifies how the groups are used.
+
+Below are examples of group definitions.  The first example is using **YAML** anchors and references.
+When using **references**, the complete **YAML** document must include the **anchors** portion of the document first.
+
+The second example shows a complete **groups** definition without the use of a **hosts** name space and it's **YAML** anchors.
+
+The field defintions in the examples below are defined in the **hosts** section.
+
+Example using **YAML** anchors and references::
+
+  groups:
+  	- &g1
+      - <<: *p1
+      	weight: 1.5
+      - <<: *p2
+      	weight: 0.5
+  	- &g2
+      - <<: *p3
+      	weight: 0.5
+      - <<: *p4
+        weight: 1.5
+ 
+Explicitly defined Example, no **YAML** referenences::
+
+  groups:
+    - &g1
+      - p1
+        host: p1.foo.com
+        protocol:
+          - scheme: http 
+            port: 80
+            health_check_url: http://192.168.1.1:80
+          - scheme: https 
+            port: 443
+            health_check_url: https://192.168.1.1:443
+        weight: 0.5
+      - p2
+        host: p2.foo.com
+        protocol:
+          - scheme: http 
+            port: 80
+            health_check_url: http://192.168.1.2:80
+          - scheme: https 
+            port: 443
+            health_check_url: https://192.168.1.2:443
+        weight: 0.5
+    - &g2
+      - p3
+        host: p3.foo.com
+        protocol:
+          - scheme: http 
+            port: 80
+            health_check_url: http://192.168.1.3:80
+          - scheme: https 
+            port: 443
+            health_check_url: https://192.168.1.3:443
+        weight: 0.5
+      - p4
+        host: p4.foo.com
+        protocol:
+          - scheme: http 
+            port: 80
+            health_check_url: http://192.168.1.4:80
+          - scheme: https 
+            port: 443
+            health_check_url: https://192.168.1.4:443
+        weight: 0.5      
+
+Strategies defintions
+=====================
+
+The **strategies** namespace defines a **YAML** list of strategies that may be applied to a **remap**
+entry using the **@strategy** tag in remap.config.
+
+Each **strategy** in the list may using the following parameters::
+
+- **strategy**: The value is the name of the strategy.
+- **policy**: The algorithm the **strategy** uses to select hosts. Currently one of the following:
+
+   #. **rr_ip**: round robin selection using the modulus of the client IP
+   #. **rr_strict**: strict round robin over the list of hosts in the primary group.
+   #. **first_live**: always selects the first host in the primary group.  Other hosts are selected when the first host fails.  
+   #. **latched**:  Same as **first_live** but primary selection sticks to whatever host was used by a previous transaction.
+   #. **consistent_hash**: hosts are selected using a **hash_key**.
+
+- **hash_key**: The hashing key used by the **consistent_hash** policy. If not specified, defaults to **path** which is the
+  same policy used in the **parent.config** implementation. Use one of::
+   #. **hostname**: Creates a hash using the **hostname** in the request URL.
+   #. **path**: (**default**) Creates a hash over the path poertion of the request URL.
+   #. **path+query**: Same as **path** but adds the **query string** in the request URL.
+   #. **path+fragment**: Same as **path** but adds the fragement portion of the URL.
+   #. **cache_key**: Uses the hash key from the **cachekey** plugin.  defaults to **path** if the **cachekey** plugin is not configured on the **remap**.
+   #. **url**: Creates a hash from the entire request url.
+   
+- **go_direct** - A boolean value indicating whether a transaction may bypass proxies and go direct to the origin. Defaults to **true**
+- **parent_is_proxy**: A boolean value which indicates if the groups of hosts are proxy caches or origins.  **true** (default) means all the hosts used in the reamp are trafficserver caches.  **false** means the hosts are origins that the next hop strategies may use for load balancing and/or failover.
+- **scheme** Indicates which scheme the strategy supports, *http* or *https*
+  - **failover**: A map of **failover** information.
+  - **max_simple_retries**: Part of the **failover** map and is an integer value of the maximum number of retries for a **simple retry** on the list of indicated response codes.  **simple retry** is used to retry an upstream request using another upstream server if the response received on from the original upstream request matches any of the response codes configured for this strategy in the **failover** map.  If no failover response codes are configured, no **simple retry** is attempted.
+
+  - **ring_mode**: Part of the **failover** map. The host ring selection mode.  Use either **exhaust_ring** or **alternate_ring**
+   #. **exhaust_ring**: when a host normally selected by the policy fails, another host is selected from the same group.  A new group is not selected until all hosts on the previous group have been exhausted.
+   #. **alternate_ring**: retry hosts are selected from groups in an alternating group fashion.
+  - **response_codes**: Part of the **failover** map.  This is a list of **http** response codes that may be used for **simple retry**.
+  - **health_check**: Part of the **failover** map.  A list of health checks. **passive** is the default and means that the state machine marks down **hosts** when a transaction timeout or connection error is detected.  **passive** is always used by the next hop strategies.  **active** means that some external process may actively health check the hosts using the defined **health check url** and mark them down using **traffic_ctl**.
+
+
+Example::
+
+ #include unit-tests/hosts.yaml
+ #
+strategies:
+  - strategy: 'strategy-1'
+    policy: consistent_hash
+    hash_key: cache_key
+    go_direct: false
+    groups:
+      - *g1
+      - *g2
+    scheme http
+    failover:
+      ring_mode: exhaust_ring
+      response_codes:
+        - 404
+        - 503
+      health_check:
+        - passive
+  - strategy: 'strategy-2'
+    policy: rr_strict
+    hash_key: cache_key
+    go_direct: true
+    groups:
+      - *g1
+      - *g2
+    scheme http
+    failover:
+      ring_mode: exhaust_ring
+      response_codes:
+        - 404
+        - 503
+      health_check:
+        - passive
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 135e821..b174b8d 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1044,6 +1044,8 @@ static const RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.url_remap.filename", RECD_STRING, ts::filename::REMAP, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
+  {RECT_CONFIG, "proxy.config.url_remap.strategies.filename", RECD_STRING, "strategies.yaml", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+  ,
   {RECT_CONFIG, "proxy.config.url_remap.remap_required", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.url_remap.pristine_host_hdr", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
diff --git a/proxy/ParentSelection.cc b/proxy/ParentSelection.cc
index 93a0a4c..96d1606 100644
--- a/proxy/ParentSelection.cc
+++ b/proxy/ParentSelection.cc
@@ -48,8 +48,6 @@ static const char *default_var   = "proxy.config.http.parent_proxies";
 static const char *retry_var     = "proxy.config.http.parent_proxy.retry_time";
 static const char *threshold_var = "proxy.config.http.parent_proxy.fail_threshold";
 
-static const char *ParentResultStr[] = {"PARENT_UNDEFINED", "PARENT_DIRECT", "PARENT_SPECIFIED", "PARENT_AGENT", "PARENT_FAIL"};
-
 //
 //  Config Callback Prototypes
 //
diff --git a/proxy/ParentSelection.h b/proxy/ParentSelection.h
index 4ac9ae5..7d1d621 100644
--- a/proxy/ParentSelection.h
+++ b/proxy/ParentSelection.h
@@ -59,12 +59,15 @@ enum ParentResultType {
   PARENT_FAIL,
 };
 
+static const char *ParentResultStr[] = {"PARENT_UNDEFINED", "PARENT_DIRECT", "PARENT_SPECIFIED", "PARENT_AGENT", "PARENT_FAIL"};
+
 enum ParentRR_t {
   P_NO_ROUND_ROBIN = 0,
   P_STRICT_ROUND_ROBIN,
   P_HASH_ROUND_ROBIN,
   P_CONSISTENT_HASH,
   P_LATCHED_ROUND_ROBIN,
+  P_UNDEFINED
 };
 
 enum ParentRetry_t {
@@ -155,6 +158,11 @@ public:
 //   between HttpTransact & the parent selection code.  The following
 ParentRecord *const extApiRecord = (ParentRecord *)0xeeeeffff;
 
+// used here to to set the number of ATSConsistentHashIter's
+// used in NextHopSelectionStrategy to limit the host group
+// size as well, group size is one to one with the number of rings
+constexpr const uint32_t MAX_GROUP_RINGS = 5;
+
 struct ParentResult {
   ParentResult() { reset(); }
   // For outside consumption
@@ -162,7 +170,7 @@ struct ParentResult {
   const char *hostname;
   int port;
   bool retry;
-  bool chash_init[2]               = {false, false};
+  bool chash_init[MAX_GROUP_RINGS] = {false};
   HostStatus_t first_choice_status = HostStatus_t::HOST_STATUS_INIT;
 
   void
@@ -250,6 +258,15 @@ struct ParentResult {
     }
   }
 
+  void
+  print()
+  {
+    printf("ParentResult - hostname: %s, port: %d, retry: %s, line_number: %d, last_parent: %d, start_parent: %d, wrap_around: %s, "
+           "last_lookup: %d, result: %s\n",
+           hostname, port, (retry) ? "true" : "false", line_number, last_parent, start_parent, (wrap_around) ? "true" : "false",
+           last_lookup, ParentResultStr[result]);
+  }
+
 private:
   // Internal use only
   //   Not to be modified by HTTP
@@ -257,12 +274,16 @@ private:
   ParentRecord *rec;
   uint32_t last_parent;
   uint32_t start_parent;
+  uint32_t last_group;
   bool wrap_around;
   bool mapWrapped[2];
   // state for consistent hash.
   int last_lookup;
-  ATSConsistentHashIter chashIter[2];
+  ATSConsistentHashIter chashIter[MAX_GROUP_RINGS];
 
+  friend class NextHopSelectionStrategy;
+  friend class NextHopRoundRobin;
+  friend class NextHopConsistentHash;
   friend class ParentConsistentHash;
   friend class ParentRoundRobin;
   friend class ParentConfigParams;
diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc
index 89e6a62..2a5e4b7 100644
--- a/proxy/http/HttpTransact.cc
+++ b/proxy/http/HttpTransact.cc
@@ -78,6 +78,187 @@ static char range_type[] = "multipart/byteranges; boundary=RANGE_SEPARATOR";
 
 extern HttpBodyFactory *body_factory;
 
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static bool
+bypass_ok(HttpTransact::State *s)
+{
+  bool r          = false;
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    // remap strategies do not support the TSHttpTxnParentProxySet API.
+    r = mp->strategy->go_direct;
+  } else if (s->parent_params) {
+    r = s->parent_result.bypass_ok();
+  }
+  return r;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static bool
+is_api_result(HttpTransact::State *s)
+{
+  bool r          = false;
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    // remap strategies do not support the TSHttpTxnParentProxySet API.
+    r = false;
+  } else if (s->parent_params) {
+    r = s->parent_result.is_api_result();
+  }
+  return r;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static unsigned
+max_retries(HttpTransact::State *s, ParentRetry_t method)
+{
+  unsigned int r  = 0;
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    // remap strategies does not support unavailable_server_responses
+    if (method == PARENT_RETRY_SIMPLE) {
+      r = mp->strategy->max_simple_retries;
+    }
+  } else if (s->parent_params) {
+    r = s->parent_result.max_retries(method);
+  }
+  return r;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static uint32_t
+numParents(HttpTransact::State *s)
+{
+  uint32_t r      = 0;
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    r = mp->strategy->num_parents;
+  } else if (s->parent_params) {
+    r = s->parent_params->numParents(&s->parent_result);
+  }
+  return r;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static bool
+parent_is_proxy(HttpTransact::State *s)
+{
+  bool r          = false;
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    r = mp->strategy->parent_is_proxy;
+  } else if (s->parent_params) {
+    r = s->parent_result.parent_is_proxy();
+  }
+  return r;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static bool
+response_is_retryable(HttpTransact::State *s, HTTPStatus response_code)
+{
+  bool r          = false;
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    if (mp->strategy->resp_codes.codes.size() > 0) {
+      r = mp->strategy->resp_codes.contains(response_code);
+    }
+  } else if (s->parent_params) {
+    r = s->parent_result.response_is_retryable(response_code);
+  }
+  return r;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static unsigned
+retry_type(HttpTransact::State *s)
+{
+  unsigned r      = PARENT_RETRY_NONE;
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    if (mp->strategy->resp_codes.codes.size() > 0) {
+      r = PARENT_RETRY_SIMPLE;
+    }
+  } else if (s->parent_params) {
+    r = s->parent_result.retry_type();
+  }
+  return r;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static void
+findParent(HttpTransact::State *s)
+{
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    return mp->strategy->findNextHop(s->state_machine->sm_id, s->parent_result, s->request_data, s->txn_conf->parent_fail_threshold,
+                                     s->txn_conf->parent_retry_time);
+  } else if (s->parent_params) {
+    return s->parent_params->findParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold,
+                                        s->txn_conf->parent_retry_time);
+  }
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static void
+markParentDown(HttpTransact::State *s)
+{
+  url_mapping *mp = s->url_map.getMapping();
+
+  if (mp && mp->strategy) {
+    return mp->strategy->markNextHopDown(s->state_machine->sm_id, s->parent_result, s->txn_conf->parent_fail_threshold,
+                                         s->txn_conf->parent_retry_time);
+  } else if (s->parent_params) {
+    return s->parent_params->markParentDown(&s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time);
+  }
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static void
+markParentUp(HttpTransact::State *s)
+{
+  url_mapping *mp = s->url_map.getMapping();
+  if (mp && mp->strategy) {
+    return mp->strategy->markNextHopUp(s->state_machine->sm_id, s->parent_result);
+  } else if (s->parent_params) {
+    return s->parent_params->markParentUp(&s->parent_result);
+  }
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static void
+nextParent(HttpTransact::State *s)
+{
+  url_mapping *mp = s->url_map.getMapping();
+  if (mp && mp->strategy) {
+    // NextHop only has a findNextHop() function.
+    return mp->strategy->findNextHop(s->state_machine->sm_id, s->parent_result, s->request_data, s->txn_conf->parent_fail_threshold,
+                                     s->txn_conf->parent_retry_time);
+  } else if (s->parent_params) {
+    return s->parent_params->nextParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold,
+                                        s->txn_conf->parent_retry_time);
+  }
+}
+
 inline static bool
 is_localhost(const char *name, int len)
 {
@@ -92,14 +273,13 @@ simple_or_unavailable_server_retry(HttpTransact::State *s)
   HTTPStatus server_response = http_hdr_status_get(s->hdr_info.server_response.m_http);
 
   TxnDebug("http_trans", "[simple_or_unavailabe_server_retry] server_response = %d, simple_retry_attempts: %d, numParents:%d ",
-           server_response, s->current.simple_retry_attempts, s->parent_params->numParents(&s->parent_result));
+           server_response, s->current.simple_retry_attempts, numParents(s));
 
   // simple retry is enabled, 0x1
-  if ((s->parent_result.retry_type() & PARENT_RETRY_SIMPLE) &&
-      s->current.simple_retry_attempts < s->parent_result.max_retries(PARENT_RETRY_SIMPLE) &&
+  if ((retry_type(s) & PARENT_RETRY_SIMPLE) && s->current.simple_retry_attempts < max_retries(s, PARENT_RETRY_SIMPLE) &&
       server_response == HTTP_STATUS_NOT_FOUND) {
     TxnDebug("http_trans", "RECEIVED A SIMPLE RETRY RESPONSE");
-    if (s->current.simple_retry_attempts < s->parent_params->numParents(&s->parent_result)) {
+    if (s->current.simple_retry_attempts < numParents(s)) {
       s->current.state      = HttpTransact::PARENT_RETRY;
       s->current.retry_type = PARENT_RETRY_SIMPLE;
       return;
@@ -109,11 +289,11 @@ simple_or_unavailable_server_retry(HttpTransact::State *s)
     }
   }
   // unavailable server retry is enabled 0x2
-  else if ((s->parent_result.retry_type() & PARENT_RETRY_UNAVAILABLE_SERVER) &&
-           s->current.unavailable_server_retry_attempts < s->parent_result.max_retries(PARENT_RETRY_UNAVAILABLE_SERVER) &&
-           s->parent_result.response_is_retryable(server_response)) {
+  else if ((retry_type(s) & PARENT_RETRY_UNAVAILABLE_SERVER) &&
+           s->current.unavailable_server_retry_attempts < max_retries(s, PARENT_RETRY_UNAVAILABLE_SERVER) &&
+           response_is_retryable(s, server_response)) {
     TxnDebug("parent_select", "RECEIVED A PARENT_RETRY_UNAVAILABLE_SERVER RESPONSE");
-    if (s->current.unavailable_server_retry_attempts < s->parent_params->numParents(&s->parent_result)) {
+    if (s->current.unavailable_server_retry_attempts < numParents(s)) {
       s->current.state      = HttpTransact::PARENT_RETRY;
       s->current.retry_type = PARENT_RETRY_UNAVAILABLE_SERVER;
       return;
@@ -265,13 +445,11 @@ find_server_and_update_current_info(HttpTransact::State *s)
     s->parent_result.result = PARENT_DIRECT;
   } else if (s->method == HTTP_WKSIDX_CONNECT && s->http_config_param->disable_ssl_parenting) {
     if (s->parent_result.result == PARENT_SPECIFIED) {
-      s->parent_params->nextParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold,
-                                   s->txn_conf->parent_retry_time);
+      nextParent(s);
     } else {
-      s->parent_params->findParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold,
-                                   s->txn_conf->parent_retry_time);
+      findParent(s);
     }
-    if (!s->parent_result.is_some() || s->parent_result.is_api_result() || s->parent_result.parent_is_proxy()) {
+    if (!s->parent_result.is_some() || is_api_result(s) || parent_is_proxy(s)) {
       TxnDebug("http_trans", "request not cacheable, so bypass parent");
       s->parent_result.result = PARENT_DIRECT;
     }
@@ -284,25 +462,21 @@ find_server_and_update_current_info(HttpTransact::State *s)
     // with respect to whether a request is cacheable or not.
     // For example, the cache_urls_that_look_dynamic variable.
     if (s->parent_result.result == PARENT_SPECIFIED) {
-      s->parent_params->nextParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold,
-                                   s->txn_conf->parent_retry_time);
+      nextParent(s);
     } else {
-      s->parent_params->findParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold,
-                                   s->txn_conf->parent_retry_time);
+      findParent(s);
     }
-    if (!s->parent_result.is_some() || s->parent_result.is_api_result() || s->parent_result.parent_is_proxy()) {
+    if (!s->parent_result.is_some() || is_api_result(s) || parent_is_proxy(s)) {
       TxnDebug("http_trans", "request not cacheable, so bypass parent");
       s->parent_result.result = PARENT_DIRECT;
     }
   } else {
     switch (s->parent_result.result) {
     case PARENT_UNDEFINED:
-      s->parent_params->findParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold,
-                                   s->txn_conf->parent_retry_time);
+      findParent(s);
       break;
     case PARENT_SPECIFIED:
-      s->parent_params->nextParent(&s->request_data, &s->parent_result, s->txn_conf->parent_fail_threshold,
-                                   s->txn_conf->parent_retry_time);
+      nextParent(s);
 
       // Hack!
       // We already have a parent that failed, if we are now told
@@ -319,8 +493,8 @@ find_server_and_update_current_info(HttpTransact::State *s)
       //   1) the config permitted us to dns the origin server
       //   2) the config permits us
       //   3) the parent was not set from API
-      if (s->http_config_param->no_dns_forward_to_parent == 0 && s->parent_result.bypass_ok() &&
-          s->parent_result.parent_is_proxy() && !s->parent_params->apiParentExists(&s->request_data)) {
+      if (s->http_config_param->no_dns_forward_to_parent == 0 && bypass_ok(s) && parent_is_proxy(s) &&
+          !s->parent_params->apiParentExists(&s->request_data)) {
         s->parent_result.result = PARENT_DIRECT;
       }
       break;
@@ -1441,7 +1615,7 @@ HttpTransact::PPDNSLookup(State *s)
   if (!s->dns_info.lookup_success) {
     // Mark parent as down due to resolving failure
     HTTP_INCREMENT_DYN_STAT(http_total_parent_marked_down_count);
-    s->parent_params->markParentDown(&s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time);
+    markParentDown(s);
     // DNS lookup of parent failed, find next parent or o.s.
     if (find_server_and_update_current_info(s) == HttpTransact::HOST_NONE) {
       ink_assert(s->current.request_to == HOST_NONE);
@@ -3325,7 +3499,7 @@ HttpTransact::handle_response_from_parent(State *s)
   // response is from a parent origin server.
   if (is_response_valid(s, &s->hdr_info.server_response) && s->current.request_to == HttpTransact::PARENT_PROXY) {
     // check for a retryable response if simple or unavailable server retry are enabled.
-    if (s->parent_result.retry_type() & (PARENT_RETRY_SIMPLE | PARENT_RETRY_UNAVAILABLE_SERVER)) {
+    if (retry_type(s) & (PARENT_RETRY_SIMPLE | PARENT_RETRY_UNAVAILABLE_SERVER)) {
       simple_or_unavailable_server_retry(s);
     }
   }
@@ -3337,13 +3511,13 @@ HttpTransact::handle_response_from_parent(State *s)
     s->current.server->connect_result = 0;
     SET_VIA_STRING(VIA_DETAIL_PP_CONNECT, VIA_DETAIL_PP_SUCCESS);
     if (s->parent_result.retry) {
-      s->parent_params->markParentUp(&s->parent_result);
+      markParentUp(s);
     }
     handle_forward_server_connection_open(s);
     break;
   case PARENT_RETRY:
     if (s->current.retry_type == PARENT_RETRY_SIMPLE) {
-      if (s->current.simple_retry_attempts >= s->parent_result.max_retries(PARENT_RETRY_SIMPLE)) {
+      if (s->current.simple_retry_attempts >= max_retries(s, PARENT_RETRY_SIMPLE)) {
         TxnDebug("http_trans", "PARENT_RETRY_SIMPLE: retried all parents, send error to client.");
         s->current.retry_type = PARENT_RETRY_NONE;
       } else {
@@ -3353,7 +3527,7 @@ HttpTransact::handle_response_from_parent(State *s)
         next_lookup           = find_server_and_update_current_info(s);
       }
     } else if (s->current.retry_type == PARENT_RETRY_UNAVAILABLE_SERVER) {
-      if (s->current.unavailable_server_retry_attempts >= s->parent_result.max_retries(PARENT_RETRY_UNAVAILABLE_SERVER)) {
+      if (s->current.unavailable_server_retry_attempts >= max_retries(s, PARENT_RETRY_UNAVAILABLE_SERVER)) {
         TxnDebug("http_trans", "PARENT_RETRY_UNAVAILABLE_SERVER: retried all parents, send error to client.");
         s->current.retry_type = PARENT_RETRY_NONE;
       } else {
@@ -3361,7 +3535,7 @@ HttpTransact::handle_response_from_parent(State *s)
         TxnDebug("http_trans", "PARENT_RETRY_UNAVAILABLE_SERVER: marking parent down and trying another.");
         s->current.retry_type = PARENT_RETRY_NONE;
         HTTP_INCREMENT_DYN_STAT(http_total_parent_marked_down_count);
-        s->parent_params->markParentDown(&s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time);
+        markParentDown(s);
         next_lookup = find_server_and_update_current_info(s);
       }
     }
@@ -3387,7 +3561,7 @@ HttpTransact::handle_response_from_parent(State *s)
     if (!is_request_retryable(s)) {
       if (s->current.state != OUTBOUND_CONGESTION) {
         HTTP_INCREMENT_DYN_STAT(http_total_parent_marked_down_count);
-        s->parent_params->markParentDown(&s->parent_result, s->txn_conf->parent_fail_threshold, s->txn_conf->parent_retry_time);
+        markParentDown(s);
       }
       s->parent_result.result = PARENT_FAIL;
       handle_parent_died(s);
@@ -7546,7 +7720,7 @@ HttpTransact::build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_r
   if (outgoing_request->method_get_wksidx() == HTTP_WKSIDX_CONNECT) {
     // CONNECT method requires a target in the URL, so always force it from the Host header.
     outgoing_request->set_url_target_from_host_field();
-  } else if (s->current.request_to == PARENT_PROXY && s->parent_result.parent_is_proxy()) {
+  } else if (s->current.request_to == PARENT_PROXY && parent_is_proxy(s)) {
     // If we have a parent proxy set the URL target field.
     if (!outgoing_request->is_target_in_url()) {
       TxnDebug("http_trans", "[build_request] adding target to URL for parent proxy");
diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h
index f181c61..7b237bc 100644
--- a/proxy/http/HttpTransact.h
+++ b/proxy/http/HttpTransact.h
@@ -707,7 +707,8 @@ public:
     bool first_dns_lookup                             = true;
 
     HttpRequestData request_data;
-    ParentConfigParams *parent_params = nullptr;
+    ParentConfigParams *parent_params                           = nullptr;
+    std::shared_ptr<NextHopSelectionStrategy> next_hop_strategy = nullptr;
     ParentResult parent_result;
     CacheControlResult cache_control;
     CacheLookupResult_t cache_lookup_result = CACHE_LOOKUP_NONE;
diff --git a/proxy/http/remap/Makefile.am b/proxy/http/remap/Makefile.am
index c1874c9..afa0de0 100644
--- a/proxy/http/remap/Makefile.am
+++ b/proxy/http/remap/Makefile.am
@@ -29,13 +29,22 @@ AM_CPPFLAGS += \
 	-I$(abs_top_srcdir)/proxy/hdrs \
 	-I$(abs_top_srcdir)/proxy/shared \
 	-I$(abs_top_srcdir)/proxy/http \
-	$(TS_INCLUDES)
+	$(TS_INCLUDES) \
+	@YAMLCPP_INCLUDES@
 
 noinst_LIBRARIES = libhttp_remap.a
 
 libhttp_remap_a_SOURCES = \
 	AclFiltering.cc \
 	AclFiltering.h \
+	NextHopSelectionStrategy.h \
+	NextHopSelectionStrategy.cc \
+	NextHopConsistentHash.h \
+	NextHopConsistentHash.cc \
+	NextHopRoundRobin.h \
+	NextHopRoundRobin.cc \
+	NextHopStrategyFactory.h \
+	NextHopStrategyFactory.cc \
 	RemapConfig.cc \
 	RemapConfig.h \
 	RemapPluginInfo.cc \
@@ -68,7 +77,7 @@ clang-tidy-local: $(libhttp_remap_a_SOURCES)
 	$(CXX_Clang_Tidy)
 
 TESTS = $(check_PROGRAMS)
-check_PROGRAMS =  test_PluginDso test_PluginFactory test_RemapPluginInfo
+check_PROGRAMS =  test_PluginDso test_PluginFactory test_RemapPluginInfo test_NextHopStrategyFactory test_NextHopRoundRobin test_NextHopConsistentHash
 
 test_PluginDso_CPPFLAGS = $(AM_CPPFLAGS) -I$(abs_top_srcdir)/tests/include -DPLUGIN_DSO_TESTS
 test_PluginDso_LIBTOOLFLAGS = --preserve-dup-deps
@@ -113,6 +122,92 @@ test_RemapPluginInfo_SOURCES = \
 	PluginDso.cc \
 	RemapPluginInfo.cc
 
+test_NextHopStrategyFactory_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-D_NH_UNIT_TESTS_ \
+	-I$(abs_top_srcdir)/tests/include \
+	$(TS_INCLUDES) \
+	@YAMLCPP_INCLUDES@
+
+test_NextHopStrategyFactory_LDADD = \
+  $(top_builddir)/src/tscpp/util/libtscpputil.la \
+  $(top_builddir)/src/tscore/libtscore.la \
+  $(top_builddir)/proxy/hdrs/libhdrs.a \
+  $(top_builddir)/iocore/eventsystem/libinkevent.a \
+  $(top_builddir)/lib/records/librecords_p.a \
+  $(top_builddir)/proxy/logging/liblogging.a \
+  $(top_builddir)/mgmt/libmgmt_p.la \
+  $(top_builddir)/iocore/utils/libinkutils.a \
+	@YAMLCPP_LIBS@ \
+	@HWLOC_LIBS@ 
+
+test_NextHopStrategyFactory_LDFLAGS = $(AM_LDFLAGS) -L$(top_builddir)/src/tscore/.libs -ltscore
+
+test_NextHopStrategyFactory_SOURCES = \
+	NextHopSelectionStrategy.cc \
+	NextHopStrategyFactory.cc \
+	NextHopRoundRobin.cc \
+	NextHopConsistentHash.cc \
+	unit-tests/test_NextHopStrategyFactory.cc \
+	unit-tests/nexthop_test_stubs.cc
+
+test_NextHopRoundRobin_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-D_NH_UNIT_TESTS_ \
+	-I$(abs_top_srcdir)/tests/include \
+	$(TS_INCLUDES) \
+	@YAMLCPP_INCLUDES@
+
+test_NextHopRoundRobin_LDADD = \
+  $(top_builddir)/src/tscpp/util/libtscpputil.la \
+  $(top_builddir)/src/tscore/libtscore.la \
+  $(top_builddir)/proxy/hdrs/libhdrs.a \
+  $(top_builddir)/iocore/eventsystem/libinkevent.a \
+  $(top_builddir)/lib/records/librecords_p.a \
+  $(top_builddir)/proxy/logging/liblogging.a \
+  $(top_builddir)/mgmt/libmgmt_p.la \
+  $(top_builddir)/iocore/utils/libinkutils.a \
+	@YAMLCPP_LIBS@ \
+	@HWLOC_LIBS@ 
+
+test_NextHopRoundRobin_LDFLAGS = $(AM_LDFLAGS) -L$(top_builddir)/src/tscore/.libs -ltscore
+
+test_NextHopRoundRobin_SOURCES = \
+	NextHopSelectionStrategy.cc \
+	NextHopStrategyFactory.cc \
+	NextHopRoundRobin.cc \
+	NextHopConsistentHash.cc \
+	unit-tests/test_NextHopRoundRobin.cc \
+	unit-tests/nexthop_test_stubs.cc
+
+test_NextHopConsistentHash_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-D_NH_UNIT_TESTS_ \
+	-I$(abs_top_srcdir)/tests/include \
+	$(TS_INCLUDES) \
+	@YAMLCPP_INCLUDES@
+
+test_NextHopConsistentHash_LDADD = \
+  $(top_builddir)/src/tscpp/util/libtscpputil.la \
+  $(top_builddir)/src/tscore/libtscore.la \
+  $(top_builddir)/proxy/hdrs/libhdrs.a \
+  $(top_builddir)/iocore/eventsystem/libinkevent.a \
+  $(top_builddir)/lib/records/librecords_p.a \
+  $(top_builddir)/proxy/logging/liblogging.a \
+  $(top_builddir)/mgmt/libmgmt_p.la \
+  $(top_builddir)/iocore/utils/libinkutils.a \
+	@YAMLCPP_LIBS@ \
+	@HWLOC_LIBS@ 
+
+test_NextHopConsistentHash_LDFLAGS = $(AM_LDFLAGS) -L$(top_builddir)/src/tscore/.libs -ltscore
+
+test_NextHopConsistentHash_SOURCES = \
+	NextHopSelectionStrategy.cc \
+	NextHopStrategyFactory.cc \
+	NextHopConsistentHash.cc \
+	NextHopRoundRobin.cc \
+	unit-tests/test_NextHopConsistentHash.cc \
+	unit-tests/nexthop_test_stubs.cc
 
 DSO_LDFLAGS = \
 	-module \
diff --git a/proxy/http/remap/NextHopConsistentHash.cc b/proxy/http/remap/NextHopConsistentHash.cc
new file mode 100644
index 0000000..ca90038
--- /dev/null
+++ b/proxy/http/remap/NextHopConsistentHash.cc
@@ -0,0 +1,402 @@
+/** @file
+
+  Implementation of nexthop consistent hash selections strategies.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <yaml-cpp/yaml.h>
+
+#include "tscore/HashSip.h"
+#include "NextHopConsistentHash.h"
+
+// hash_key strings.
+constexpr std::string_view hash_key_url           = "url";
+constexpr std::string_view hash_key_hostname      = "hostname";
+constexpr std::string_view hash_key_path          = "path";
+constexpr std::string_view hash_key_path_query    = "path+query";
+constexpr std::string_view hash_key_path_fragment = "path+fragment";
+constexpr std::string_view hash_key_cache         = "cache_key";
+
+static HostRecord *
+chash_lookup(std::shared_ptr<ATSConsistentHash> ring, uint64_t hash_key, ATSConsistentHashIter *iter, bool *wrapped,
+             ATSHash64Sip24 *hash, bool *hash_init, uint64_t sm_id)
+{
+  HostRecord *host_rec = nullptr;
+
+  if (*hash_init == false) {
+    host_rec   = static_cast<HostRecord *>(ring->lookup_by_hashval(hash_key, iter, wrapped));
+    *hash_init = true;
+  } else {
+    host_rec = static_cast<HostRecord *>(ring->lookup(nullptr, iter, wrapped, hash));
+  }
+
+  return host_rec;
+}
+
+NextHopConsistentHash::~NextHopConsistentHash()
+{
+  NH_Debug(NH_DEBUG_TAG, "destructor called for strategy named: %s", strategy_name.c_str());
+}
+
+bool
+NextHopConsistentHash::Init(const YAML::Node &n)
+{
+  ATSHash64Sip24 hash;
+
+  try {
+    if (n["hash_key"]) {
+      auto hash_key_val = n["hash_key"].Scalar();
+      if (hash_key_val == hash_key_url) {
+        hash_key = NH_URL_HASH_KEY;
+      } else if (hash_key_val == hash_key_hostname) {
+        hash_key = NH_HOSTNAME_HASH_KEY;
+      } else if (hash_key_val == hash_key_path) {
+        hash_key = NH_PATH_HASH_KEY;
+      } else if (hash_key_val == hash_key_path_query) {
+        hash_key = NH_PATH_QUERY_HASH_KEY;
+      } else if (hash_key_val == hash_key_path_fragment) {
+        hash_key = NH_PATH_FRAGMENT_HASH_KEY;
+      } else if (hash_key_val == hash_key_cache) {
+        hash_key = NH_CACHE_HASH_KEY;
+      } else {
+        hash_key = NH_PATH_HASH_KEY;
+        NH_Note("Invalid 'hash_key' value, '%s', for the strategy named '%s', using default '%s'.", hash_key_val.c_str(),
+                strategy_name.c_str(), hash_key_path.data());
+      }
+    }
+  } catch (std::exception &ex) {
+    NH_Note("Error parsing the strategy named '%s' due to '%s', this strategy will be ignored.", strategy_name.c_str(), ex.what());
+    return false;
+  }
+
+  bool result = NextHopSelectionStrategy::Init(n);
+  if (!result) {
+    return false;
+  }
+
+  // load up the hash rings.
+  for (uint32_t i = 0; i < groups; i++) {
+    std::shared_ptr<ATSConsistentHash> hash_ring = std::make_shared<ATSConsistentHash>();
+    for (uint32_t j = 0; j < host_groups[i].size(); j++) {
+      // ATSConsistentHash needs the raw pointer.
+      HostRecord *p = host_groups[i][j].get();
+      // need to copy the 'hash_string' or 'hostname' cstring to 'name' for insertion into ATSConsistentHash.
+      if (!p->hash_string.empty()) {
+        p->name = const_cast<char *>(p->hash_string.c_str());
+      } else {
+        p->name = const_cast<char *>(p->hostname.c_str());
+      }
+      p->group_index = host_groups[i][j]->group_index;
+      p->host_index  = host_groups[i][j]->host_index;
+      hash_ring->insert(p, p->weight, &hash);
+      NH_Debug(NH_DEBUG_TAG, "Loading hash rings - ring: %d, host record: %d, name: %s, hostname: %s, stategy: %s", i, j, p->name,
+               p->hostname.c_str(), strategy_name.c_str());
+    }
+    hash.clear();
+    rings.push_back(std::move(hash_ring));
+  }
+  return true;
+}
+
+// returns a hash key calculated from the request and 'hash_key' configuration
+// parameter.
+uint64_t
+NextHopConsistentHash::getHashKey(uint64_t sm_id, HttpRequestData *hrdata, ATSHash64 *h)
+{
+  URL *url                   = hrdata->hdr->url_get();
+  URL *ps_url                = nullptr;
+  int len                    = 0;
+  const char *url_string_ref = nullptr;
+
+  // calculate a hash using the selected config.
+  switch (hash_key) {
+  case NH_URL_HASH_KEY:
+    url_string_ref = url->string_get_ref(&len, true);
+    if (url_string_ref && len > 0) {
+      h->update(url_string_ref, len);
+      NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] url hash string: %s", sm_id, url_string_ref);
+    }
+    break;
+  // hostname hash
+  case NH_HOSTNAME_HASH_KEY:
+    url_string_ref = url->host_get(&len);
+    if (url_string_ref && len > 0) {
+      h->update(url_string_ref, len);
+    }
+    break;
+  // path + query string
+  case NH_PATH_QUERY_HASH_KEY:
+    url_string_ref = url->path_get(&len);
+    h->update("/", 1);
+    if (url_string_ref && len > 0) {
+      h->update(url_string_ref, len);
+    }
+    url_string_ref = url->query_get(&len);
+    if (url_string_ref && len > 0) {
+      h->update("?", 1);
+      h->update(url_string_ref, len);
+    }
+    break;
+  // path + fragment hash
+  case NH_PATH_FRAGMENT_HASH_KEY:
+    url_string_ref = url->path_get(&len);
+    h->update("/", 1);
+    if (url_string_ref && len > 0) {
+      h->update(url_string_ref, len);
+    }
+    url_string_ref = url->fragment_get(&len);
+    if (url_string_ref && len > 0) {
+      h->update("?", 1);
+      h->update(url_string_ref, len);
+    }
+    break;
+  // use the cache key created by the cache-key plugin.
+  case NH_CACHE_HASH_KEY:
+    ps_url = *(hrdata->cache_info_parent_selection_url);
+    if (ps_url) {
+      url_string_ref = ps_url->string_get_ref(&len);
+      if (url_string_ref && len > 0) {
+        NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] using parent selection over-ride string:'%.*s'.", sm_id, len, url_string_ref);
+        h->update(url_string_ref, len);
+      }
+    } else {
+      url_string_ref = url->path_get(&len);
+      h->update("/", 1);
+      if (url_string_ref && len > 0) {
+        NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] the parent selection over-ride url is not set, using default path: %s.", sm_id,
+                 url_string_ref);
+        h->update(url_string_ref, len);
+      }
+    }
+    break;
+  // use the path as the hash, default.
+  case NH_PATH_HASH_KEY:
+  default:
+    url_string_ref = url->path_get(&len);
+    h->update("/", 1);
+    if (url_string_ref && len > 0) {
+      h->update(url_string_ref, len);
+    }
+    break;
+  }
+
+  h->final();
+  return h->get();
+}
+
+void
+NextHopConsistentHash::findNextHop(const uint64_t sm_id, ParentResult &result, RequestData &rdata, const uint64_t fail_threshold,
+                                   const uint64_t retry_time, time_t now)
+{
+  time_t _now       = now;
+  bool firstcall    = false;
+  bool nextHopRetry = false;
+  bool wrapped      = false;
+  std::vector<bool> wrap_around(groups, false);
+  uint32_t cur_ring = 0; // there is a hash ring for each host group
+  uint64_t hash_key = 0;
+  uint32_t lookups  = 0;
+  ATSHash64Sip24 hash;
+  HttpRequestData *request_info    = static_cast<HttpRequestData *>(&rdata);
+  HostRecord *hostRec              = nullptr;
+  std::shared_ptr<HostRecord> pRec = nullptr;
+  HostStatus &pStatus              = HostStatus::instance();
+  HostStatus_t host_stat           = HostStatus_t::HOST_STATUS_INIT;
+  HostStatRec *hst                 = nullptr;
+
+  if (result.line_number == -1 && result.result == PARENT_UNDEFINED) {
+    firstcall = true;
+  }
+
+  if (firstcall) {
+    NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] firstcall, line_number: %d, result: %s", sm_id, result.line_number,
+             ParentResultStr[result.result]);
+    result.line_number = distance;
+    cur_ring           = 0;
+    for (uint32_t i = 0; i < groups; i++) {
+      result.chash_init[i] = false;
+      wrap_around[i]       = false;
+    }
+  } else {
+    NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] not firstcall, line_number: %d, result: %s", sm_id, result.line_number,
+             ParentResultStr[result.result]);
+    switch (ring_mode) {
+    case NH_ALTERNATE_RING:
+      if (groups > 1) {
+        cur_ring = (result.last_group + 1) % groups;
+      } else {
+        cur_ring = result.last_group;
+      }
+      break;
+    case NH_EXHAUST_RING:
+    default:
+      if (!wrapped) {
+        cur_ring = result.last_group;
+      } else if (groups > 1) {
+        cur_ring = (result.last_group + 1) % groups;
+      }
+      break;
+    }
+  }
+
+  // Do the initial parent look-up.
+  hash_key = getHashKey(sm_id, request_info, &hash);
+
+  do { // search until we've selected a different parent if !firstcall
+    std::shared_ptr<ATSConsistentHash> r = rings[cur_ring];
+    hostRec = chash_lookup(r, hash_key, &result.chashIter[cur_ring], &wrapped, &hash, &result.chash_init[cur_ring], sm_id);
+    wrap_around[cur_ring] = wrapped;
+    lookups++;
+    // the 'available' flag is maintained in 'host_groups' and not the hash ring.
+    if (hostRec) {
+      pRec = host_groups[hostRec->group_index][hostRec->host_index];
+      if (firstcall) {
+        hst                        = (pRec) ? pStatus.getHostStatus(pRec->hostname.c_str()) : nullptr;
+        result.first_choice_status = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP;
+        break;
+      }
+    } else {
+      pRec = nullptr;
+    }
+  } while (pRec && result.hostname && strcmp(pRec->hostname.c_str(), result.hostname) == 0);
+
+  NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] Initial parent lookups: %d", sm_id, lookups);
+
+  // ----------------------------------------------------------------------------------------------------
+  // Validate initial parent look-up and perform additional look-ups if required.
+  // ----------------------------------------------------------------------------------------------------
+
+  hst       = (pRec) ? pStatus.getHostStatus(pRec->hostname.c_str()) : nullptr;
+  host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP;
+  // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason
+  // ignore the down status and mark it as avaialble
+  if ((pRec && ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) {
+    if (hst->reasons == Reason::SELF_DETECT) {
+      host_stat = HOST_STATUS_UP;
+    }
+  }
+  if (!pRec || (pRec && !pRec->available) || host_stat == HOST_STATUS_DOWN) {
+    do {
+      // check if an unavailable server is now retryable, use it if it is.
+      if (pRec && !pRec->available && host_stat == HOST_STATUS_UP) {
+        _now == 0 ? _now = time(nullptr) : _now = now;
+        // check if the host is retryable.  It's retryable if the retry window has elapsed
+        if ((pRec->failedAt + retry_time) < static_cast<unsigned>(_now)) {
+          nextHopRetry       = true;
+          result.last_parent = pRec->host_index;
+          result.last_lookup = pRec->group_index;
+          result.retry       = nextHopRetry;
+          result.result      = PARENT_SPECIFIED;
+          NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] next hop %s is now retryable, marked it available.", sm_id, pRec->hostname.c_str());
+          break;
+        }
+      }
+      switch (ring_mode) {
+      case NH_ALTERNATE_RING:
+        if (groups > 0) {
+          cur_ring = (pRec->group_index + 1) % groups;
+        }
+        break;
+      case NH_EXHAUST_RING:
+      default:
+        if (wrap_around[cur_ring] && groups > 1) {
+          cur_ring = (cur_ring + 1) % groups;
+        }
+        break;
+      }
+      std::shared_ptr<ATSConsistentHash> r = rings[cur_ring];
+      hostRec = chash_lookup(r, hash_key, &result.chashIter[cur_ring], &wrapped, &hash, &result.chash_init[cur_ring], sm_id);
+      wrap_around[cur_ring] = wrapped;
+      lookups++;
+      if (hostRec) {
+        pRec      = host_groups[hostRec->group_index][hostRec->host_index];
+        hst       = (pRec) ? pStatus.getHostStatus(pRec->hostname.c_str()) : nullptr;
+        host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP;
+        // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason
+        // ignore the down status and mark it as avaialble
+        if ((pRec && ignore_self_detect) && (hst && hst->status == HOST_STATUS_DOWN)) {
+          if (hst->reasons == Reason::SELF_DETECT) {
+            host_stat = HOST_STATUS_UP;
+          }
+        }
+        NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] Selected a new parent: %s, available: %s, wrapped: %s, lookups: %d.", sm_id,
+                 pRec->hostname.c_str(), (pRec->available) ? "true" : "false", (wrapped) ? "true" : "false", lookups);
+        // use available host.
+        if (pRec->available && host_stat == HOST_STATUS_UP) {
+          break;
+        }
+      } else {
+        pRec = nullptr;
+      }
+      bool all_wrapped = true;
+      for (uint32_t c = 0; c < groups; c++) {
+        if (wrap_around[c] == false) {
+          all_wrapped = false;
+        }
+      }
+      if (all_wrapped) {
+        NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] No available parents.", sm_id);
+        if (pRec) {
+          pRec = nullptr;
+        }
+        break;
+      }
+    } while (!pRec || (pRec && !pRec->available) || host_stat == HOST_STATUS_DOWN);
+  }
+
+  // ----------------------------------------------------------------------------------------------------
+  // Validate and return the final result.
+  // ----------------------------------------------------------------------------------------------------
+
+  // use the available or marked for retry parent.
+  hst       = (pRec) ? pStatus.getHostStatus(pRec->hostname.c_str()) : nullptr;
+  host_stat = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP;
+
+  if (pRec && host_stat == HOST_STATUS_UP && (pRec->available || result.retry)) {
+    result.result      = PARENT_SPECIFIED;
+    result.hostname    = pRec->hostname.c_str();
+    result.last_parent = pRec->host_index;
+    result.last_lookup = result.last_group = cur_ring;
+    switch (scheme) {
+    case NH_SCHEME_NONE:
+    case NH_SCHEME_HTTP:
+      result.port = pRec->getPort(scheme);
+      break;
+    case NH_SCHEME_HTTPS:
+      result.port = pRec->getPort(scheme);
+      break;
+    }
+    result.retry = nextHopRetry;
+    ink_assert(result.hostname != nullptr);
+    ink_assert(result.port != 0);
+    NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] Chosen parent: %s.%d", sm_id, result.hostname, result.port);
+  } else {
+    if (go_direct == true) {
+      result.result = PARENT_DIRECT;
+    } else {
+      result.result = PARENT_FAIL;
+    }
+    result.hostname = nullptr;
+    result.port     = 0;
+    result.retry    = false;
+  }
+
+  return;
+}
diff --git a/proxy/http/remap/NextHopConsistentHash.h b/proxy/http/remap/NextHopConsistentHash.h
new file mode 100644
index 0000000..47fcf9e
--- /dev/null
+++ b/proxy/http/remap/NextHopConsistentHash.h
@@ -0,0 +1,54 @@
+/** @file
+
+  Implementation of nexthop consistent hash selections strategies.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <vector>
+#include "NextHopSelectionStrategy.h"
+
+enum NHHashKeyType {
+  NH_URL_HASH_KEY = 0,
+  NH_HOSTNAME_HASH_KEY,
+  NH_PATH_HASH_KEY, // default, consistent hash uses the request url path
+  NH_PATH_QUERY_HASH_KEY,
+  NH_PATH_FRAGMENT_HASH_KEY,
+  NH_CACHE_HASH_KEY
+};
+
+class NextHopConsistentHash : public NextHopSelectionStrategy
+{
+  std::vector<std::shared_ptr<ATSConsistentHash>> rings;
+
+  uint64_t getHashKey(uint64_t sm_id, HttpRequestData *hrdata, ATSHash64 *h);
+
+public:
+  NHHashKeyType hash_key = NH_PATH_HASH_KEY;
+
+  NextHopConsistentHash() = delete;
+  NextHopConsistentHash(const std::string_view name, const NHPolicyType &policy) : NextHopSelectionStrategy(name, policy) {}
+  ~NextHopConsistentHash();
+  bool Init(const YAML::Node &n);
+  void findNextHop(const uint64_t sm_id, ParentResult &result, RequestData &rdata, const uint64_t fail_threshold,
+                   const uint64_t retry_time, time_t now = 0) override;
+};
diff --git a/proxy/http/remap/NextHopRoundRobin.cc b/proxy/http/remap/NextHopRoundRobin.cc
new file mode 100644
index 0000000..8bdcbc5
--- /dev/null
+++ b/proxy/http/remap/NextHopRoundRobin.cc
@@ -0,0 +1,219 @@
+/** @file
+
+  Implementation of various round robin nexthop selections strategies.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <mutex>
+#include <yaml-cpp/yaml.h>
+
+#include "NextHopRoundRobin.h"
+
+NextHopRoundRobin::~NextHopRoundRobin()
+{
+  NH_Debug(NH_DEBUG_TAG, "destructor called for strategy named: %s", strategy_name.c_str());
+}
+
+void
+NextHopRoundRobin::findNextHop(const uint64_t sm_id, ParentResult &result, RequestData &rdata, const uint64_t fail_threshold,
+                               const uint64_t retry_time, time_t now)
+{
+  time_t _now      = now;
+  bool firstcall   = true;
+  bool parentUp    = false;
+  bool parentRetry = false;
+  bool wrapped     = result.wrap_around;
+  std::vector<bool> wrap_around(groups, false);
+  uint32_t cur_hst_index = 0;
+  uint32_t cur_grp_index = 0;
+  uint32_t hst_size      = host_groups[cur_grp_index].size();
+  uint32_t start_group   = 0;
+  uint32_t start_host    = 0;
+  std::shared_ptr<HostRecord> cur_host;
+  HostStatus &pStatus           = HostStatus::instance();
+  HttpRequestData *request_info = static_cast<HttpRequestData *>(&rdata);
+  HostStatus_t host_stat        = HostStatus_t::HOST_STATUS_UP;
+
+  if (result.line_number != -1 && result.result != PARENT_UNDEFINED) {
+    firstcall = false;
+  }
+
+  if (firstcall) {
+    // distance is the index into the strategies map, this is the equivalent to the old line_number in parent.config.
+    result.line_number = distance;
+    NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] first call , cur_grp_index: %d, cur_hst_index: %d, distance: %d", sm_id, cur_grp_index,
+             cur_hst_index, distance);
+    switch (policy_type) {
+    case NH_FIRST_LIVE:
+      result.start_parent = cur_hst_index = 0;
+      cur_grp_index                       = 0;
+      break;
+    case NH_RR_STRICT: {
+      std::lock_guard<std::mutex> lock(_mutex);
+      cur_hst_index = result.start_parent = this->hst_index;
+      cur_grp_index                       = 0;
+      this->hst_index                     = (this->hst_index + 1) % hst_size;
+    } break;
+    case NH_RR_IP:
+      cur_grp_index = 0;
+      if (rdata.get_client_ip() != nullptr) {
+        cur_hst_index = result.start_parent = ntohl(ats_ip_hash(rdata.get_client_ip())) % hst_size;
+      } else {
+        cur_hst_index = this->hst_index;
+      }
+      break;
+    case NH_RR_LATCHED:
+      cur_grp_index = 0;
+      cur_hst_index = result.start_parent = latched_index;
+      break;
+    default:
+      ink_assert(0);
+      break;
+    }
+    cur_host = host_groups[cur_grp_index][cur_hst_index];
+    NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] first call, cur_grp_index: %d, cur_hst_index: %d", sm_id, cur_grp_index, cur_hst_index);
+  } else {
+    NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] next call, cur_grp_index: %d, cur_hst_index: %d, distance: %d", sm_id, cur_grp_index,
+             cur_hst_index, distance);
+    // Move to next parent due to failure
+    latched_index = cur_hst_index = (result.last_parent + 1) % hst_size;
+    cur_host                      = host_groups[cur_grp_index][cur_hst_index];
+
+    // Check to see if we have wrapped around
+    if (static_cast<unsigned int>(cur_hst_index) == result.start_parent) {
+      // We've wrapped around so bypass if we can
+      if (go_direct == true) {
+        result.result = PARENT_DIRECT;
+      } else {
+        result.result = PARENT_FAIL;
+      }
+      result.hostname    = nullptr;
+      result.port        = 0;
+      result.wrap_around = true;
+      return;
+    }
+  }
+  start_group = cur_grp_index;
+  start_host  = cur_hst_index;
+
+  // Verify that the 'cur_hst' is available or retryable, if not loop through the array of parents seeing if any are up or
+  // should be retried
+  do {
+    HostStatRec *hst = pStatus.getHostStatus(cur_host->hostname.c_str());
+    host_stat        = (hst) ? hst->status : HostStatus_t::HOST_STATUS_UP;
+    // if the config ignore_self_detect is set to true and the host is down due to SELF_DETECT reason
+    // ignore the down status and mark it as avaialble
+    if (ignore_self_detect && (hst && hst->status == HOST_STATUS_DOWN)) {
+      if (hst->reasons == Reason::SELF_DETECT) {
+        host_stat = HOST_STATUS_UP;
+      }
+    }
+
+    NH_Debug(NH_DEBUG_TAG,
+             "[%" PRIu64 "] Selected a parent, %s,  failCount (faileAt: %d failCount: %d), FailThreshold: %" PRIu64
+             ", request_info->xact_start: %ld",
+             sm_id, cur_host->hostname.c_str(), (unsigned)cur_host->failedAt, cur_host->failCount, fail_threshold,
+             request_info->xact_start);
+    // check if 'cur_host' is available, mark it up if it is.
+    if ((cur_host->failedAt == 0) || (cur_host->failCount < fail_threshold)) {
+      if (host_stat == HOST_STATUS_UP) {
+        NH_Debug(NH_DEBUG_TAG,
+                 "[%" PRIu64
+                 "] Selecting a parent, %s,  due to little failCount (faileAt: %d failCount: %d), FailThreshold: %" PRIu64,
+                 sm_id, cur_host->hostname.c_str(), (unsigned)cur_host->failedAt, cur_host->failCount, fail_threshold);
+        parentUp = true;
+      }
+    } else { // if not available, check to see if it can be retried.  If so, set the retry flag and temporairly mark it as
+             // available.
+      _now == 0 ? _now = time(nullptr) : _now = now;
+      if (((result.wrap_around) || (cur_host->failedAt + retry_time) < static_cast<unsigned>(_now)) &&
+          host_stat == HOST_STATUS_UP) {
+        // Reuse the parent
+        parentUp    = true;
+        parentRetry = true;
+        NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "]  NextHop marked for retry %s:%d", sm_id, cur_host->hostname.c_str(),
+                 host_groups[cur_grp_index][cur_hst_index]->getPort(scheme));
+      } else { // not retryable or available.
+        parentUp = false;
+      }
+    }
+    NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] parentUp: %s, hostname: %s, host status: %s", sm_id, parentUp ? "true" : "false",
+             cur_host->hostname.c_str(), HostStatusNames[host_stat]);
+
+    // The selected host is available or retryable, return the search result.
+    if (parentUp == true && host_stat != HOST_STATUS_DOWN) {
+      NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] status for %s: %s", sm_id, cur_host->hostname.c_str(), HostStatusNames[host_stat]);
+      result.result      = PARENT_SPECIFIED;
+      result.hostname    = cur_host->hostname.c_str();
+      result.port        = cur_host->getPort(scheme);
+      result.last_parent = cur_hst_index;
+      result.last_group  = cur_grp_index;
+      result.retry       = parentRetry;
+      ink_assert(result.hostname != nullptr);
+      ink_assert(result.port != 0);
+      NH_Debug(NH_DEBUG_TAG, "[%" PRIu64 "] Chosen parent = %s.%d", sm_id, result.hostname, result.port);
+      return;
+    }
+
+    // only one host group is available, find another host if we have not wrapped.
+    if (groups == 1) {
+      latched_index = cur_hst_index = (cur_hst_index + 1) % hst_size;
+      if (start_host == cur_hst_index) {
+        wrap_around[cur_grp_index] = wrapped = result.wrap_around = true;
+      }
+    } else {                                // search the fail over groups.
+      if (ring_mode == NH_ALTERNATE_RING) { // use alternating ring mode.
+        cur_grp_index = (cur_grp_index + 1) % groups;
+        hst_size      = host_groups[cur_grp_index].size();
+        if (cur_grp_index == start_group) {
+          latched_index = cur_hst_index = (cur_hst_index + 1) % hst_size;
+          if (cur_hst_index == start_host) {
+            wrapped = wrap_around[cur_grp_index] = result.wrap_around = true;
+          }
+        }
+      } else { // use the exhaust ring mode.
+        latched_index = cur_hst_index = (cur_hst_index + 1) % hst_size;
+        if (cur_hst_index == start_host) {
+          wrap_around[cur_grp_index] = true;
+          cur_grp_index              = (cur_grp_index + 1) % groups;
+          if (cur_grp_index == start_group) {
+            wrapped = wrap_around[cur_grp_index] = result.wrap_around = true;
+          } else {
+            start_host = cur_hst_index = 0;
+          }
+        }
+      }
+    }
+    cur_host = host_groups[cur_grp_index][cur_hst_index];
+    NH_Debug(
+      NH_DEBUG_TAG,
+      "[%" PRIu64 "] host: %s, groups: %d, cur_grp_index: %d, cur_hst_index: %d, wrapped: %s, start_group: %d, start_host: %d",
+      sm_id, cur_host->hostname.c_str(), groups, cur_grp_index, cur_hst_index, wrapped ? "true" : "false", start_group, start_host);
+  } while (!wrapped);
+
+  if (go_direct == true) {
+    result.result = PARENT_DIRECT;
+  } else {
+    result.result = PARENT_FAIL;
+  }
+
+  result.hostname = nullptr;
+  result.port     = 0;
+}
diff --git a/proxy/http/remap/NextHopRoundRobin.h b/proxy/http/remap/NextHopRoundRobin.h
new file mode 100644
index 0000000..80b05e0
--- /dev/null
+++ b/proxy/http/remap/NextHopRoundRobin.h
@@ -0,0 +1,45 @@
+/** @file
+
+  Implementation of various round robin nexthop selections strategies.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include <mutex>
+#include "NextHopSelectionStrategy.h"
+
+class NextHopRoundRobin : public NextHopSelectionStrategy
+{
+  std::mutex _mutex;
+  uint32_t latched_index = 0;
+
+public:
+  NextHopRoundRobin() = delete;
+  NextHopRoundRobin(const std::string_view &name, const NHPolicyType &policy) : NextHopSelectionStrategy(name, policy) {}
+  ~NextHopRoundRobin();
+  bool
+  Init(const YAML::Node &n)
+  {
+    return NextHopSelectionStrategy::Init(n);
+  }
+  void findNextHop(const uint64_t sm_id, ParentResult &result, RequestData &rdata, const uint64_t fail_threshold,
+                   const uint64_t retry_time, time_t now = 0) override;
+};
diff --git a/proxy/http/remap/NextHopSelectionStrategy.cc b/proxy/http/remap/NextHopSelectionStrategy.cc
new file mode 100644
index 0000000..99be984
--- /dev/null
+++ b/proxy/http/remap/NextHopSelectionStrategy.cc
@@ -0,0 +1,377 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <yaml-cpp/yaml.h>
+#include "I_Machine.h"
+#include "NextHopSelectionStrategy.h"
+
+// ring mode strings
+constexpr std::string_view alternate_rings = "alternate_ring";
+constexpr std::string_view exhaust_rings   = "exhaust_ring";
+
+// health check strings
+constexpr std::string_view active_health_check  = "active";
+constexpr std::string_view passive_health_check = "passive";
+
+constexpr const char *policy_strings[] = {"NH_UNDEFINED", "NH_FIRST_LIVE", "NH_RR_STRICT",
+                                          "NH_RR_IP",     "NH_RR_LATCHED", "NH_CONSISTENT_HASH"};
+
+NextHopSelectionStrategy::NextHopSelectionStrategy(const std::string_view &name, const NHPolicyType &policy)
+{
+  strategy_name = name;
+  policy_type   = policy;
+  NH_Debug(NH_DEBUG_TAG, "Using a selection strategy of type %s", policy_strings[policy]);
+}
+
+//
+// parse out the data for this strategy.
+//
+bool
+NextHopSelectionStrategy::Init(const YAML::Node &n)
+{
+  NH_Debug(NH_DEBUG_TAG, "calling Init()");
+
+  try {
+    if (n["scheme"]) {
+      auto scheme_val = n["scheme"].Scalar();
+      if (scheme_val == "http") {
+        scheme = NH_SCHEME_HTTP;
+      } else if (scheme_val == "https") {
+        scheme = NH_SCHEME_HTTPS;
+      } else {
+        scheme = NH_SCHEME_NONE;
+        NH_Note("Invalid 'scheme' value, '%s', for the strategy named '%s', setting to NH_SCHEME_NONE", scheme_val.c_str(),
+                strategy_name.c_str());
+      }
+    }
+
+    // go_direct config.
+    if (n["go_direct"]) {
+      go_direct = n["go_direct"].as<bool>();
+    }
+
+    // parent_is_proxy config.
+    if (n["parent_is_proxy"]) {
+      parent_is_proxy = n["parent_is_proxy"].as<bool>();
+    }
+
+    // ignore_self_detect
+    if (n["ignore_self_detect"]) {
+      ignore_self_detect = n["ignore_self_detect"].as<bool>();
+    }
+
+    // failover node.
+    YAML::Node failover_node;
+    if (n["failover"]) {
+      failover_node = n["failover"];
+      if (failover_node["ring_mode"]) {
+        auto ring_mode_val = failover_node["ring_mode"].Scalar();
+        if (ring_mode_val == alternate_rings) {
+          ring_mode = NH_ALTERNATE_RING;
+        } else if (ring_mode_val == exhaust_rings) {
+          ring_mode = NH_EXHAUST_RING;
+        } else {
+          ring_mode = NH_ALTERNATE_RING;
+          NH_Note("Invalid 'ring_mode' value, '%s', for the strategy named '%s', using default '%s'.", ring_mode_val.c_str(),
+                  strategy_name.c_str(), alternate_rings.data());
+        }
+      }
+      if (failover_node["max_simple_retries"]) {
+        max_simple_retries = failover_node["max_simple_retries"].as<int>();
+      }
+
+      YAML::Node resp_codes_node;
+      if (failover_node["response_codes"]) {
+        resp_codes_node = failover_node["response_codes"];
+        if (resp_codes_node.Type() != YAML::NodeType::Sequence) {
+          NH_Error("Error in the response_codes definition for the strategy named '%s', skipping response_codes.",
+                   strategy_name.c_str());
+        } else {
+          for (unsigned int k = 0; k < resp_codes_node.size(); ++k) {
+            auto code = resp_codes_node[k].as<int>();
+            if (code > 300 && code < 599) {
+              resp_codes.add(code);
+            } else {
+              NH_Note("Skipping invalid response code '%d' for the strategy named '%s'.", code, strategy_name.c_str());
+            }
+          }
+          resp_codes.sort();
+        }
+      }
+      YAML::Node health_check_node;
+      if (failover_node["health_check"]) {
+        health_check_node = failover_node["health_check"];
+        if (health_check_node.Type() != YAML::NodeType::Sequence) {
+          NH_Error("Error in the health_check definition for the strategy named '%s', skipping health_checks.",
+                   strategy_name.c_str());
+        } else {
+          for (auto it = health_check_node.begin(); it != health_check_node.end(); ++it) {
+            auto health_check = it->as<std::string>();
+            if (health_check.compare(active_health_check) == 0) {
+              health_checks.active = true;
+            }
+            if (health_check.compare(passive_health_check) == 0) {
+              health_checks.passive = true;
+            }
+          }
+        }
+      }
+    }
+
+    // parse and load the host data
+    YAML::Node groups_node;
+    if (n["groups"]) {
+      groups_node = n["groups"];
+      // a groups list is required.
+      if (groups_node.Type() != YAML::NodeType::Sequence) {
+        throw std::invalid_argument("Invalid groups definition, expected a sequence, '" + strategy_name + "' cannot be loaded.");
+      } else {
+        Machine *mach      = Machine::instance();
+        HostStatus &h_stat = HostStatus::instance();
+        uint32_t grp_size  = groups_node.size();
+        if (grp_size > MAX_GROUP_RINGS) {
+          NH_Note("the groups list exceeds the maximum of %d for the strategy '%s'. Only the first %d groups will be configured.",
+                  MAX_GROUP_RINGS, strategy_name.c_str(), MAX_GROUP_RINGS);
+          groups = MAX_GROUP_RINGS;
+        } else {
+          groups = groups_node.size();
+        }
+        // resize the hosts vector.
+        host_groups.reserve(groups);
+        // loop through the groups
+        for (unsigned int grp = 0; grp < groups; ++grp) {
+          YAML::Node hosts_list = groups_node[grp];
+
+          // a list of hosts is required.
+          if (hosts_list.Type() != YAML::NodeType::Sequence) {
+            throw std::invalid_argument("Invalid hosts definition, expected a sequence, '" + strategy_name + "' cannot be loaded.");
+          } else {
+            // loop through the hosts list.
+            std::vector<std::shared_ptr<HostRecord>> hosts_inner;
+
+            for (unsigned int hst = 0; hst < hosts_list.size(); ++hst) {
+              std::shared_ptr<HostRecord> host_rec = std::make_shared<HostRecord>(hosts_list[hst].as<HostRecord>());
+              host_rec->group_index                = grp;
+              host_rec->host_index                 = hst;
+              if (mach->is_self(host_rec->hostname.c_str())) {
+                h_stat.setHostStatus(host_rec->hostname.c_str(), HostStatus_t::HOST_STATUS_DOWN, 0, Reason::SELF_DETECT);
+              }
+              hosts_inner.push_back(std::move(host_rec));
+              num_parents++;
+            }
+            host_groups.push_back(std::move(hosts_inner));
+          }
+        }
+      }
+    }
+  } catch (std::exception &ex) {
+    NH_Note("Error parsing the strategy named '%s' due to '%s', this strategy will be ignored.", strategy_name.c_str(), ex.what());
+    return false;
+  }
+
+  return true;
+}
+
+void
+NextHopSelectionStrategy::markNextHopDown(const uint64_t sm_id, ParentResult &result, const uint64_t fail_threshold,
+                                          const uint64_t retry_time, time_t now)
+{
+  time_t _now;
+  now == 0 ? _now = time(nullptr) : _now = now;
+  uint32_t new_fail_count                = 0;
+
+  //  Make sure that we are being called back with with a
+  //  result structure with a selected parent.
+  if (result.result != PARENT_SPECIFIED) {
+    return;
+  }
+  // If we were set through the API we currently have not failover
+  //   so just return fail
+  if (result.is_api_result()) {
+    ink_assert(0);
+    return;
+  }
+  uint32_t hst_size = host_groups[result.last_group].size();
+  ink_assert(result.last_parent < hst_size);
+  std::shared_ptr<HostRecord> h = host_groups[result.last_group][result.last_parent];
+
+  // If the parent has already been marked down, just increment
+  //   the failure count.  If this is the first mark down on a
+  //   parent we need to both set the failure time and set
+  //   count to one. If this was the result of a retry, we
+  //   must update move the failedAt timestamp to now so that we
+  //   continue negative cache the parent
+  if (h->failedAt == 0 || result.retry == true) {
+    { // start of lock_guard scope.
+      std::lock_guard<std::mutex> lock(h->_mutex);
+      if (h->failedAt == 0) {
+        // Mark the parent failure time.
+        h->failedAt = _now;
+        if (result.retry == false) {
+          new_fail_count = h->failCount = 1;
+        }
+      } else if (result.retry == true) {
+        h->failedAt = _now;
+      }
+    } // end of lock_guard scope
+    NH_Note("[%" PRIu64 "] NextHop %s marked as down %s:%d", sm_id, (result.retry) ? "retry" : "initially", h->hostname.c_str(),
+            h->getPort(scheme));
+
+  } else {
+    int old_count = 0;
+
+    // if the last failure was outside the retry window, set the failcount to 1 and failedAt to now.
+    { // start of lock_guard_scope
+      std::lock_guard<std::mutex> lock(h->_mutex);
+      if ((h->failedAt + retry_time) < static_cast<unsigned>(_now)) {
+        h->failCount = 1;
+        h->failedAt  = _now;
+      } else {
+        old_count = h->failCount = 1;
+      }
+      new_fail_count = old_count + 1;
+    } // end of lock_guard
+    NH_Debug("parent_select", "[%" PRIu64 "] Parent fail count increased to %d for %s:%d", sm_id, new_fail_count,
+             h->hostname.c_str(), h->getPort(scheme));
+  }
+
+  if (new_fail_count >= fail_threshold) {
+    h->set_unavailable();
+    NH_Note("[%" PRIu64 "] Failure threshold met failcount:%d >= threshold:%" PRIu64 ", http parent proxy %s:%d marked down", sm_id,
+            new_fail_count, fail_threshold, h->hostname.c_str(), h->getPort(scheme));
+    NH_Debug("parent_select", "[%" PRIu64 "] NextHop %s:%d marked unavailable, h->available=%s", sm_id, h->hostname.c_str(),
+             h->getPort(scheme), (h->available) ? "true" : "false");
+  }
+}
+
+void
+NextHopSelectionStrategy::markNextHopUp(const uint64_t sm_id, ParentResult &result)
+{
+  uint32_t old_count = 0;
+  //  Make sure that we are being called back with with a
+  //   result structure with a parent that is being retried
+  ink_assert(result.retry == true);
+  if (result.result != PARENT_SPECIFIED) {
+    return;
+  }
+  // If we were set through the API we currently have not failover
+  //   so just return fail
+  if (result.is_api_result()) {
+    ink_assert(0);
+    return;
+  }
+  uint32_t hst_size = host_groups[result.last_group].size();
+  ink_assert(result.last_parent < hst_size);
+  std::shared_ptr<HostRecord> h = host_groups[result.last_group][result.last_parent];
+
+  if (!h->available) {
+    h->set_available();
+  }
+  if (h->available && old_count > 0) {
+    NH_Note("[%" PRIu64 "] http parent proxy %s:%d restored", sm_id, h->hostname.c_str(), h->getPort(scheme));
+  }
+}
+
+namespace YAML
+{
+template <> struct convert<HostRecord> {
+  static bool
+  decode(const Node &node, HostRecord &nh)
+  {
+    YAML::Node nd;
+    bool merge_tag_used = false;
+
+    // check for YAML merge tag.
+    if (node["<<"]) {
+      nd             = node["<<"];
+      merge_tag_used = true;
+    } else {
+      nd = node;
+    }
+
+    // lookup the hostname
+    if (nd["host"]) {
+      nh.hostname = nd["host"].Scalar();
+    } else {
+      throw std::invalid_argument("Invalid host definition, missing host name.");
+    }
+
+    // lookup the port numbers supported by this host.
+    YAML::Node proto = nd["protocol"];
+
+    if (proto.Type() != YAML::NodeType::Sequence) {
+      throw std::invalid_argument("Invalid host protocol definition, expected a sequence.");
+    } else {
+      for (unsigned int ii = 0; ii < proto.size(); ii++) {
+        YAML::Node protocol_node       = proto[ii];
+        std::shared_ptr<NHProtocol> pr = std::make_shared<NHProtocol>(protocol_node.as<NHProtocol>());
+        nh.protocols.push_back(std::move(pr));
+      }
+    }
+
+    // get the host's weight
+    YAML::Node weight;
+    if (merge_tag_used) {
+      weight    = node["weight"];
+      nh.weight = weight.as<float>();
+    } else if ((weight = nd["weight"])) {
+      nh.weight = weight.as<float>();
+    } else {
+      NH_Note("No weight is defined for the host '%s', using default 1.0", nh.hostname.data());
+      nh.weight = 1.0;
+    }
+
+    // get the host's optional hash_string
+    YAML::Node hash;
+    if ((hash = nd["hash_string"])) {
+      nh.hash_string = hash.Scalar();
+    }
+
+    return true;
+  }
+};
+
+template <> struct convert<NHProtocol> {
+  static bool
+  decode(const Node &node, NHProtocol &nh)
+  {
+    if (node["scheme"]) {
+      if (node["scheme"].Scalar() == "http") {
+        nh.scheme = NH_SCHEME_HTTP;
+      } else if (node["scheme"].Scalar() == "https") {
+        nh.scheme = NH_SCHEME_HTTPS;
+      } else {
+        nh.scheme = NH_SCHEME_NONE;
+      }
+    }
+    if (node["port"]) {
+      nh.port = node["port"].as<int>();
+    }
+    if (node["health_check_url"]) {
+      nh.health_check_url = node["health_check_url"].Scalar();
+    }
+    return true;
+  }
+};
+}; // namespace YAML
+// namespace YAML
diff --git a/proxy/http/remap/NextHopSelectionStrategy.h b/proxy/http/remap/NextHopSelectionStrategy.h
new file mode 100644
index 0000000..200c94c
--- /dev/null
+++ b/proxy/http/remap/NextHopSelectionStrategy.h
@@ -0,0 +1,215 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include "ParentSelection.h"
+
+#ifndef _NH_UNIT_TESTS_
+#define NH_Debug(tag, ...) Debug(tag, __VA_ARGS__)
+#define NH_Error(...) DiagsError(DL_Error, __VA_ARGS__)
+#define NH_Note(...) DiagsError(DL_Note, __VA_ARGS__)
+#define NH_Warn(...) DiagsError(DL_Warning, __VA_ARGS__)
+#else
+#include "unit-tests/nexthop_test_stubs.h"
+#endif /* _NH_UNIT_TESTS_ */
+
+constexpr const char *NH_DEBUG_TAG = "next_hop";
+
+namespace YAML
+{
+class Node;
+}
+
+enum NHPolicyType {
+  NH_UNDEFINED = 0,
+  NH_FIRST_LIVE,     // first available nexthop
+  NH_RR_STRICT,      // strict round robin
+  NH_RR_IP,          // round robin by client ip.
+  NH_RR_LATCHED,     // latched to available next hop.
+  NH_CONSISTENT_HASH // consistent hashing strategy.
+};
+
+enum NHSchemeType { NH_SCHEME_NONE = 0, NH_SCHEME_HTTP, NH_SCHEME_HTTPS };
+
+enum NHRingMode { NH_ALTERNATE_RING = 0, NH_EXHAUST_RING };
+
+enum NH_HHealthCheck { NH_ACTIVE, NH_PASSIVE };
+
+// response codes container
+struct ResponseCodes {
+  ResponseCodes(){};
+  std::vector<short> codes;
+  void
+  add(short code)
+  {
+    codes.push_back(code);
+  }
+  bool
+  contains(short code)
+  {
+    return std::binary_search(codes.begin(), codes.end(), code);
+  }
+  void
+  sort()
+  {
+    std::sort(codes.begin(), codes.end());
+  }
+};
+
+struct HealthChecks {
+  bool active  = false;
+  bool passive = false;
+};
+
+struct NHProtocol {
+  NHSchemeType scheme;
+  uint32_t port;
+  std::string health_check_url;
+};
+
+struct HostRecord : ATSConsistentHashNode {
+  std::mutex _mutex;
+  std::string hostname;
+  time_t failedAt;
+  uint32_t failCount;
+  time_t upAt;
+  float weight;
+  std::string hash_string;
+  int host_index;
+  int group_index;
+  std::vector<std::shared_ptr<NHProtocol>> protocols;
+
+  // construct without locking the _mutex.
+  HostRecord()
+  {
+    hostname    = "";
+    failedAt    = 0;
+    failCount   = 0;
+    upAt        = 0;
+    weight      = 0;
+    hash_string = "";
+    host_index  = -1;
+    group_index = -1;
+    available   = true;
+  }
+
+  // copy constructor to avoid copying the _mutex.
+  HostRecord(const HostRecord &o)
+  {
+    hostname    = o.hostname;
+    failedAt    = o.failedAt;
+    failCount   = o.failCount;
+    upAt        = o.upAt;
+    weight      = o.weight;
+    hash_string = "";
+    host_index  = -1;
+    group_index = -1;
+    available   = true;
+    protocols   = o.protocols;
+  }
+
+  // assign without copying the _mutex.
+  HostRecord &
+  operator=(const HostRecord &o)
+  {
+    hostname    = o.hostname;
+    failedAt    = o.failedAt;
+    upAt        = o.upAt;
+    weight      = o.weight;
+    hash_string = o.hash_string;
+    host_index  = o.host_index;
+    group_index = o.group_index;
+    available   = o.available;
+    protocols   = o.protocols;
+    return *this;
+  }
+
+  // locks the record when marking this host down.
+  void
+  set_unavailable()
+  {
+    if (available) {
+      std::lock_guard<std::mutex> lock(_mutex);
+      failedAt  = time(nullptr);
+      available = false;
+    }
+  }
+
+  // locks the record when marking this host up.
+  void
+  set_available()
+  {
+    if (!available) {
+      std::lock_guard<std::mutex> lock(_mutex);
+      failedAt  = 0;
+      failCount = 0;
+      upAt      = time(nullptr);
+      available = true;
+    }
+  }
+
+  int
+  getPort(NHSchemeType scheme)
+  {
+    int port = 0;
+    for (uint32_t i = 0; i < protocols.size(); i++) {
+      if (protocols[i]->scheme == scheme) {
+        port = protocols[i]->port;
+        break;
+      }
+    }
+    return port;
+  }
+};
+
+class NextHopSelectionStrategy
+{
+public:
+  NextHopSelectionStrategy();
+  NextHopSelectionStrategy(const std::string_view &name, const NHPolicyType &type);
+  virtual ~NextHopSelectionStrategy(){};
+  bool Init(const YAML::Node &n);
+  virtual void findNextHop(const uint64_t sm_id, ParentResult &result, RequestData &rdata, const uint64_t fail_threshold,
+                           const uint64_t retry_time, time_t now = 0) = 0;
+  void markNextHopDown(const uint64_t sm_id, ParentResult &result, const uint64_t fail_threshold, const uint64_t retry_time,
+                       time_t now = 0);
+  void markNextHopUp(const uint64_t sm_id, ParentResult &result);
+
+  std::string strategy_name;
+  bool go_direct           = true;
+  bool parent_is_proxy     = true;
+  bool ignore_self_detect  = false;
+  NHPolicyType policy_type = NH_UNDEFINED;
+  NHSchemeType scheme      = NH_SCHEME_NONE;
+  NHRingMode ring_mode     = NH_ALTERNATE_RING;
+  ResponseCodes resp_codes;
+  HealthChecks health_checks;
+  std::vector<std::vector<std::shared_ptr<HostRecord>>> host_groups;
+  uint32_t max_simple_retries = 1;
+  uint32_t groups             = 0;
+  uint32_t grp_index          = 0;
+  uint32_t hst_index          = 0;
+  uint32_t num_parents        = 0;
+  uint32_t distance           = 0; // index into the strategies list.
+};
diff --git a/proxy/http/remap/NextHopStrategyFactory.cc b/proxy/http/remap/NextHopStrategyFactory.cc
new file mode 100644
index 0000000..6574ef5
--- /dev/null
+++ b/proxy/http/remap/NextHopStrategyFactory.cc
@@ -0,0 +1,259 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <yaml-cpp/yaml.h>
+
+#include <fstream>
+#include <string.h>
+
+#include "NextHopStrategyFactory.h"
+#include "NextHopConsistentHash.h"
+#include "NextHopRoundRobin.h"
+
+NextHopStrategyFactory::NextHopStrategyFactory(const char *file)
+{
+  YAML::Node config;
+  YAML::Node strategies;
+  std::stringstream doc;
+  std::unordered_set<std::string> include_once;
+  std::string_view fn = file;
+
+  // strategy policies.
+  constexpr std::string_view consistent_hash = "consistent_hash";
+  constexpr std::string_view first_live      = "first_live";
+  constexpr std::string_view rr_strict       = "rr_strict";
+  constexpr std::string_view rr_ip           = "rr_ip";
+  constexpr std::string_view latched         = "latched";
+
+  strategies_loaded    = true;
+  const char *basename = fn.substr(fn.find_last_of('/') + 1).data();
+
+  // load the strategies yaml config file.
+  try {
+    NH_Note("%s loading ...", basename);
+    loadConfigFile(fn.data(), doc, include_once);
+
+    config = YAML::Load(doc);
+    if (config.IsNull()) {
+      NH_Note("No NextHop strategy configs were loaded.");
+      strategies_loaded = false;
+    } else {
+      strategies = config["strategies"];
+      if (strategies.Type() != YAML::NodeType::Sequence) {
+        NH_Error("malformed %s file, expected a 'strategies' sequence", basename);
+        strategies_loaded = false;
+      }
+    }
+    // loop through the strategies document.
+    for (unsigned int i = 0; i < strategies.size(); ++i) {
+      YAML::Node strategy = strategies[i];
+      auto name           = strategy["strategy"].as<std::string>();
+      auto policy         = strategy["policy"];
+      if (!policy) {
+        NH_Error("No policy is defined for the strategy named '%s', this strategy will be ignored.", name.c_str());
+        continue;
+      }
+      auto policy_value        = policy.Scalar();
+      NHPolicyType policy_type = NH_UNDEFINED;
+
+      if (policy_value == consistent_hash) {
+        policy_type = NH_CONSISTENT_HASH;
+      } else if (policy_value == first_live) {
+        policy_type = NH_FIRST_LIVE;
+      } else if (policy_value == rr_strict) {
+        policy_type = NH_RR_STRICT;
+      } else if (policy_value == rr_ip) {
+        policy_type = NH_RR_IP;
+      } else if (policy_value == latched) {
+        policy_type = NH_RR_LATCHED;
+      }
+      if (policy_type == NH_UNDEFINED) {
+        NH_Error("Invalid policy '%s' for the strategy named '%s', this strategy will be ignored.", policy_value.c_str(),
+                 name.c_str());
+      } else {
+        createStrategy(name, policy_type, strategy);
+      }
+    }
+  } catch (std::exception &ex) {
+    NH_Note("%s", ex.what());
+    strategies_loaded = false;
+  }
+  if (strategies_loaded) {
+    NH_Note("%s finished loading", basename);
+  }
+}
+
+NextHopStrategyFactory::~NextHopStrategyFactory()
+{
+  NH_Debug(NH_DEBUG_TAG, "destroying NextHopStrategyFactory");
+}
+
+void
+NextHopStrategyFactory::createStrategy(const std::string &name, const NHPolicyType policy_type, const YAML::Node &node)
+{
+  std::shared_ptr<NextHopSelectionStrategy> strat;
+  std::shared_ptr<NextHopRoundRobin> strat_rr;
+  std::shared_ptr<NextHopConsistentHash> strat_chash;
+
+  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());
+    return;
+  }
+
+  switch (policy_type) {
+  case NH_FIRST_LIVE:
+  case NH_RR_STRICT:
+  case NH_RR_IP:
+  case NH_RR_LATCHED:
+    strat_rr = std::make_shared<NextHopRoundRobin>(name, policy_type);
+    if (strat_rr->Init(node)) {
+      _strategies.emplace(std::make_pair(std::string(name), strat_rr));
+    } else {
+      strat.reset();
+    }
+    break;
+  case NH_CONSISTENT_HASH:
+    strat_chash = std::make_shared<NextHopConsistentHash>(name, policy_type);
+    if (strat_chash->Init(node)) {
+      _strategies.emplace(std::make_pair(std::string(name), strat_chash));
+    } else {
+      strat_chash.reset();
+    }
+    break;
+  default: // handles P_UNDEFINED, no strategy is added
+    break;
+  };
+}
+
+std::shared_ptr<NextHopSelectionStrategy>
+NextHopStrategyFactory::strategyInstance(const char *name)
+{
+  std::shared_ptr<NextHopSelectionStrategy> ps_strategy;
+
+  if (!strategies_loaded) {
+    NH_Error("no strategy configurations were defined, see defintions in '%s' file", fn.c_str());
+    return nullptr;
+  } else {
+    auto it = _strategies.find(name);
+    if (it == _strategies.end()) {
+      // NH_Error("no strategy found for name: %s", name);
+      return nullptr;
+    } else {
+      ps_strategy           = it->second;
+      ps_strategy->distance = std::distance(_strategies.begin(), it);
+    }
+  }
+
+  return ps_strategy;
+}
+
+/*
+ * loads the contents of a file into a std::stringstream document.  If the file has a '#include file'
+ * directive, that 'file' is read into the document beginning at the the point where the
+ * '#include' was found. This allows the 'strategy' and 'hosts' yaml files to be separate.  The
+ * 'strategy' yaml file would then normally have the '#include hosts.yml' in it's begining.
+ */
+void
+NextHopStrategyFactory::loadConfigFile(const std::string fileName, std::stringstream &doc,
+                                       std::unordered_set<std::string> &include_once)
+{
+  const char *sep = " \t";
+  char *tok, *last;
+  struct stat buf;
+  std::string line;
+
+  if (stat(fileName.c_str(), &buf) == -1) {
+    std::string err_msg = strerror(errno);
+    throw std::invalid_argument("Unable to stat '" + fileName + "': " + err_msg);
+  }
+
+  // if fileName is a directory, concatenate all '.yaml' files alphanumerically
+  // into a single document stream.  No #include is supported.
+  if (S_ISDIR(buf.st_mode)) {
+    DIR *dir               = nullptr;
+    struct dirent *dir_ent = nullptr;
+    std::vector<std::string_view> files;
+
+    NH_Note("loading strategy YAML files from the directory %s", fileName.c_str());
+    if ((dir = opendir(fileName.c_str())) == nullptr) {
+      std::string err_msg = strerror(errno);
+      throw std::invalid_argument("Unable to open the directory '" + fileName + "': " + err_msg);
+    } else {
+      while ((dir_ent = readdir(dir)) != nullptr) {
+        // filename should be greater that 6 characters to have a '.yaml' suffix.
+        if (strlen(dir_ent->d_name) < 6) {
+          continue;
+        }
+        std::string_view sv = dir_ent->d_name;
+        if (sv.find(".yaml", sv.size() - 5) == sv.size() - 5) {
+          files.push_back(sv);
+        }
+      }
+      // sort the files alphanumerically
+      std::sort(files.begin(), files.end(),
+                [](const std::string_view lhs, const std::string_view rhs) { return lhs.compare(rhs) < 0; });
+
+      for (uint32_t i = 0; i < files.size(); i++) {
+        std::ifstream file(fileName + "/" + files[i].data());
+        if (file.is_open()) {
+          while (std::getline(file, line)) {
+            if (line[0] == '#') {
+              // continue;
+            }
+            doc << line << "\n";
+          }
+          file.close();
+        } else {
+          throw std::invalid_argument("Unable to open and read '" + fileName + "/" + files[i].data() + "'");
+        }
+      }
+    }
+  } else {
+    std::ifstream file(fileName);
+    if (file.is_open()) {
+      while (std::getline(file, line)) {
+        if (line[0] == '#') {
+          tok = strtok_r(const_cast<char *>(line.c_str()), sep, &last);
+          if (tok != nullptr && strcmp(tok, "#include") == 0) {
+            std::string f = strtok_r(nullptr, sep, &last);
+            if (include_once.find(f) == include_once.end()) {
+              include_once.insert(f);
+              // try to load included file.
+              try {
+                loadConfigFile(f, doc, include_once);
+              } catch (std::exception &ex) {
+                throw std::invalid_argument("Unable to open included file '" + f + "' from '" + fileName + "'");
+              }
+            }
+          }
+        } else {
+          doc << line << "\n";
+        }
+      }
+      file.close();
+    } else {
+      throw std::invalid_argument("Unable to open and read '" + fileName + "'");
+    }
+  }
+}
diff --git a/proxy/http/remap/NextHopStrategyFactory.h b/proxy/http/remap/NextHopStrategyFactory.h
new file mode 100644
index 0000000..3ef5cdc
--- /dev/null
+++ b/proxy/http/remap/NextHopStrategyFactory.h
@@ -0,0 +1,54 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <sstream>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "tscore/Diags.h"
+#include "NextHopSelectionStrategy.h"
+
+namespace YAML
+{
+class Node;
+};
+
+class NextHopStrategyFactory
+{
+public:
+  NextHopStrategyFactory() = delete;
+  NextHopStrategyFactory(const char *file);
+  ~NextHopStrategyFactory();
+  std::shared_ptr<NextHopSelectionStrategy> strategyInstance(const char *name);
+
+  bool strategies_loaded;
+
+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, const YAML::Node &node);
+  std::unordered_map<std::string, std::shared_ptr<NextHopSelectionStrategy>> _strategies;
+};
diff --git a/proxy/http/remap/RemapConfig.cc b/proxy/http/remap/RemapConfig.cc
index 63bca48..985aee7 100644
--- a/proxy/http/remap/RemapConfig.cc
+++ b/proxy/http/remap/RemapConfig.cc
@@ -691,6 +691,14 @@ remap_check_option(const char **argv, int argc, unsigned long findmode, int *_re
           idx = i;
         }
         ret_flags |= REMAP_OPTFLG_INTERNAL;
+      } else if (!strncasecmp(argv[i], "strategy=", 9)) {
+        if ((findmode & REMAP_OPTFLG_STRATEGY) != 0) {
+          idx = i;
+        }
+        if (argptr) {
+          *argptr = &argv[i][9];
+        }
+        ret_flags |= REMAP_OPTFLG_STRATEGY;
       } else {
         Warning("ignoring invalid remap option '%s'", argv[i]);
       }
@@ -1262,6 +1270,25 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti)
       }
     }
 
+    // check for a 'strategy' and if wire it up if one exists.
+    if ((bti->remap_optflg & REMAP_OPTFLG_STRATEGY) != 0 &&
+        (maptype == FORWARD_MAP || maptype == FORWARD_MAP_REFERER || maptype == FORWARD_MAP_WITH_RECV_PORT)) {
+      const char *strategy = strchr(bti->argv[0], static_cast<int>('='));
+      if (strategy == nullptr) {
+        errStr = "missing 'strategy' name argument, unable to add mapping rule";
+        goto MAP_ERROR;
+      } else {
+        strategy++;
+        new_mapping->strategy = bti->rewrite->strategyFactory->strategyInstance(strategy);
+        if (!new_mapping->strategy) {
+          snprintf(errStrBuf, sizeof(errStrBuf), "no strategy named '%s' is defined in the config", strategy);
+          errStr = errStrBuf;
+          goto MAP_ERROR;
+        }
+        Debug("url_rewrite_regex", "mapped the 'strategy' named %s", strategy);
+      }
+    }
+
     // Check "remap" plugin options and load .so object
     if ((bti->remap_optflg & REMAP_OPTFLG_PLUGIN) != 0 &&
         (maptype == FORWARD_MAP || maptype == FORWARD_MAP_REFERER || maptype == FORWARD_MAP_WITH_RECV_PORT)) {
diff --git a/proxy/http/remap/RemapConfig.h b/proxy/http/remap/RemapConfig.h
index 8cac3d8..276b121 100644
--- a/proxy/http/remap/RemapConfig.h
+++ b/proxy/http/remap/RemapConfig.h
@@ -38,6 +38,7 @@ class UrlRewrite;
 #define REMAP_OPTFLG_ACTION 0x0020u           /* "action=" option (used for ACL filtering) */
 #define REMAP_OPTFLG_INTERNAL 0x0040u         /* only allow internal requests to hit this remap */
 #define REMAP_OPTFLG_IN_IP 0x0080u            /* "in_ip=" option (used for ACL filtering)*/
+#define REMAP_OPTFLG_STRATEGY 0x0100u         /* "strategy=" the name of the nexthop selection strategy */
 #define REMAP_OPTFLG_MAP_ID 0x0800u           /* associate a map ID with this rule */
 #define REMAP_OPTFLG_INVERT 0x80000000u       /* "invert" the rule (for src_ip at least) */
 #define REMAP_OPTFLG_ALL_FILTERS (REMAP_OPTFLG_METHOD | REMAP_OPTFLG_SRC_IP | REMAP_OPTFLG_ACTION | REMAP_OPTFLG_INTERNAL)
diff --git a/proxy/http/remap/RemapProcessor.cc b/proxy/http/remap/RemapProcessor.cc
index 5092886..3af350f 100644
--- a/proxy/http/remap/RemapProcessor.cc
+++ b/proxy/http/remap/RemapProcessor.cc
@@ -154,6 +154,11 @@ RemapProcessor::finish_remap(HttpTransact::State *s, UrlRewrite *table)
   if (!map) {
     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);
 
diff --git a/proxy/http/remap/UrlMapping.h b/proxy/http/remap/UrlMapping.h
index c6de768..2fa52be 100644
--- a/proxy/http/remap/UrlMapping.h
+++ b/proxy/http/remap/UrlMapping.h
@@ -34,6 +34,8 @@
 #include "tscore/Regex.h"
 #include "tscore/List.h"
 
+class NextHopSelectionStrategy;
+
 /**
  * Used to store http referer strings (and/or regexp)
  **/
@@ -109,6 +111,7 @@ public:
   bool ip_allow_check_enabled_p      = false;
   acl_filter_rule *filter            = nullptr; // acl filtering (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;
 
   int
   getRank() const
diff --git a/proxy/http/remap/UrlRewrite.cc b/proxy/http/remap/UrlRewrite.cc
index e720e7d..aab1b21 100644
--- a/proxy/http/remap/UrlRewrite.cc
+++ b/proxy/http/remap/UrlRewrite.cc
@@ -84,6 +84,11 @@ UrlRewrite::load()
   /* Initialize the plugin factory */
   pluginFactory.setRuntimeDir(RecConfigReadRuntimeDir()).addSearchDir(RecConfigReadPluginDir());
 
+  /* Initialize the next hop strategy factory */
+  std::string sf = RecConfigReadConfigPath("proxy.config.url_remap.strategies.filename", "strategies.yaml");
+  Debug("url_rewrite_regex", "strategyFactory file: %s", sf.c_str());
+  strategyFactory = new NextHopStrategyFactory(sf.c_str());
+
   if (0 == this->BuildTable(config_file_path)) {
     _valid = true;
     if (is_debug_tag_set("url_rewrite")) {
@@ -109,6 +114,7 @@ UrlRewrite::~UrlRewrite()
 
   /* Deactivate the factory when all SM are gone for sure. */
   pluginFactory.deactivate();
+  delete strategyFactory;
 }
 
 /** Sets the reverse proxy flag. */
diff --git a/proxy/http/remap/UrlRewrite.h b/proxy/http/remap/UrlRewrite.h
index 054e631..7419edd 100644
--- a/proxy/http/remap/UrlRewrite.h
+++ b/proxy/http/remap/UrlRewrite.h
@@ -30,6 +30,7 @@
 #include "HttpTransact.h"
 #include "tscore/Regex.h"
 #include "PluginFactory.h"
+#include "NextHopStrategyFactory.h"
 
 #include <memory>
 
@@ -210,6 +211,7 @@ public:
   int num_rules_forward_with_recv_port = 0;
 
   PluginFactory pluginFactory;
+  NextHopStrategyFactory *strategyFactory = nullptr;
 
 private:
   bool _valid = false;
diff --git a/proxy/http/remap/unit-tests/combined.yaml b/proxy/http/remap/unit-tests/combined.yaml
new file mode 100644
index 0000000..9033dda
--- /dev/null
+++ b/proxy/http/remap/unit-tests/combined.yaml
@@ -0,0 +1,170 @@
+# @file
+#
+#  Unit test data strategy.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# 
+# unit test combined hosts and strategies, combined.yaml example
+#
+hosts:
+  - &p1 # shorthand name of host object, with an "anchor name"
+    host: p1.foo.com  # name or IP of host
+    hash_string: slsklslsk # optional hash string that replaces the hostname in consistent hashing.
+    protocol:
+      - scheme: http
+        port: 80
+        health_check_url: http://192.168.1.1:80
+      - scheme: https 
+        port: 443
+        health_check_url: https://192.168.1.1:443
+  - &p2
+    host: p2.foo.com
+    protocol:
+      - scheme: http
+        port: 80
+        health_check_url: http://192.168.1.2:80
+      - scheme: https 
+        port: 443
+        health_check_url: https://192.168.1.2:443
+  - &s1
+    host: s1.bar.com
+    protocol:
+      - scheme: http
+        port: 8080
+        health_check_url: http://192.168.2.1:8080
+      - scheme: https
+        port: 8443
+        health_check_url: https://192.168.2.1:8443
+  - &s2
+    host: s2.bar.com
+    protocol:
+      - scheme: http
+        port: 8080
+        health_check_url: http://192.168.2.2:8080
+      - scheme: https
+        port: 8443
+        health_check_url: https://192.168.2.2:8443
+groups:
+  - &g1
+    - <<: *p1
+      weight: 0.5
+    - <<: *p2
+      weight: 0.5
+  - &g2
+    - <<: *s1
+      weight: 2.0
+    - <<: *s2
+      weight: 1.0
+strategies:
+  - strategy: 'mid-tier-north'
+    policy: rr_ip # Selection strategy policy: Enum of 'consistent_hash' or 'first_live' or 'rr_strict' or 'rr_ip' or 'latched'
+    go_direct: true # transactions may routed directly to the origin true/false default is true.
+    parent_is_proxy: false  # next hop hosts  are origin servers when set to 'false', defaults to true and indicates next hop hosts are ats cache's.
+    groups: # groups of hosts, these groups are used as rings in consistent hash and arrays of host groups for round_robin.
+      - *g1
+      - *g2
+    scheme: http # enumerated, 'http' or 'https'. by default uses the remapped scheme
+    failover: 
+      max_simple_retries: 2 # default is 1, indicates the maximum number of simple retries for the listed response codes.
+      ring_mode: exhaust_ring # enumerated as exhaust_ring or alternate_ring
+          #1) in 'exhaust_ring' mode all the servers in a ring are exhausted before failing over to secondary ring
+          #2) in 'alternate_ring' mode causes the failover to another server in secondary ring.
+      response_codes: # defines the responses codes for failover in exhaust_ring mode
+        - 404
+        - 502
+        - 503
+      health_check: # specifies the list of healthchecks that should be considered for failover. A list of enums: 'passive' or 'active'
+        - passive
+        - active
+  - strategy: 'mid-tier-south'
+    policy: latched
+    go_direct: false 
+    parent_is_proxy: false  # next hop hosts  are origin servers
+    ignore_self_detect: false
+    groups:
+      - *g1
+      - *g2
+    scheme: http 
+    failover: 
+      max_simple_retries: 2 
+      ring_mode: alternate_ring
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
+  - strategy: 'mid-tier-east'
+    policy: first_live
+    go_direct: false 
+    parent_is_proxy: false  # next hop hosts  are origin servers
+    ignore_self_detect: true
+    groups:
+      - *g1
+      - *g2
+    scheme: https 
+    failover: 
+      max_simple_retries: 2 
+      ring_mode: alternate_ring 
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check:
+        - passive
+  - strategy: 'mid-tier-west'
+    policy: rr_strict
+    go_direct: true 
+    parent_is_proxy: false  # next hop hosts  are origin servers
+    groups:
+      - *g1
+      - *g2
+    scheme: https 
+    failover: 
+      max_simple_retries: 2 
+      ring_mode: exhaust_ring 
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - active
+  - strategy: 'mid-tier-midwest'
+    policy: consistent_hash
+    hash_key: cache_key 
+    go_direct: true 
+    parent_is_proxy: false  # next hop hosts  are origin servers
+    groups:
+      - *g1
+      - *g2
+    scheme: https 
+    failover: 
+      max_simple_retries: 2 
+      ring_mode: exhaust_ring 
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - active
+
diff --git a/proxy/http/remap/unit-tests/consistent-hash-tests.yaml b/proxy/http/remap/unit-tests/consistent-hash-tests.yaml
new file mode 100644
index 0000000..6c8b5f2
--- /dev/null
+++ b/proxy/http/remap/unit-tests/consistent-hash-tests.yaml
@@ -0,0 +1,171 @@
+# @file
+#
+#  Unit test data consistent-hash-tests.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# 
+# unit testing strategies for NextHopConsistentHash.
+#
+strategies:
+  - strategy: 'consistent-hash-1'
+    policy: consistent_hash 
+    hash_key: path 
+    groups: 
+      - &g1
+        - host: p1.foo.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.1.1:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.1:443
+          weight: 1.0
+        - host: p2.foo.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.1.2:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.2:443
+          weight: 1.0
+      - &g2
+        - host: s1.bar.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.2.1:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.2.1:443
+          weight: 1.0
+        - host: s2.bar.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.2.2:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.2.2:443
+          weight: 1.0
+      - &g3
+        - host: q1.bar.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.3.1:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.3.1:443
+          weight: 1.0
+        - host: q2.bar.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.3.2:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.3.2:443
+          weight: 1.0
+    scheme: http 
+    failover: 
+      ring_mode: exhaust_ring 
+      response_codes: 
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
+  - strategy: 'consistent-hash-2'
+    policy: consistent_hash 
+    hash_key: path 
+    go_direct: false
+    groups: 
+      - &g1
+        - host: c1.foo.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.1.1:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.1:443
+          weight: 1.0
+        - host: c2.foo.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.1.2:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.2:443
+          weight: 1.0
+      - &g2
+        - host: c3.bar.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.2.1:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.2.1:443
+          weight: 1.0
+        - host: c4.bar.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.2.2:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.2.2:443
+          weight: 1.0
+      - &g3
+        - host: c5.bar.com
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.3.1:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.3.1:443
+          weight: 1.0
+        - host: c6.bar.com
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.3.2:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.3.2:443
+          weight: 1.0
+    scheme: http 
+    failover: 
+      ring_mode: alternate_ring 
+      response_codes: 
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
diff --git a/proxy/http/remap/unit-tests/hosts.yaml b/proxy/http/remap/unit-tests/hosts.yaml
new file mode 100644
index 0000000..616e316
--- /dev/null
+++ b/proxy/http/remap/unit-tests/hosts.yaml
@@ -0,0 +1,71 @@
+# @file
+#
+#  Unit test data hosts.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# For unit testing hosts.yaml
+#
+hosts:
+  - &p1
+    host: 'p1.foo.com'
+    protocol:
+      - scheme: http
+        port: 80
+        health_check_url: 'http://192.168.1.1:80'
+      - scheme: https
+        port: 443
+        health_check_url: 'https://192.168.1.1:443'
+  - &p2
+    host: 'p2.foo.com'
+    protocol:
+      - scheme: http
+        port: 80
+        health_check_url: 'http://192.168.1.2:80'
+  - &p3
+    host: 'p3.foo.com'
+    protocol:
+      - scheme: http
+        port: 8080
+        health_check_url: 'http://192.168.1.3:8080'
+      - scheme: https
+        port:  8443
+        health_check_url: 'https://192.168.1.3:8443'
+  - &p4
+    host: 'p4.foo.com'
+    protocol:
+      - scheme: http 
+        port: 8080
+        health_check_url: 'http://192.168.1.4:8080'
+      - scheme: https
+        port: 8443
+        health_check_url: 'https://192.168.1.4:8443'
+groups:
+  - &g1
+    - <<: *p1
+      weight: 1.5
+    - <<: *p2
+      weight: 1.5
+  - &g2
+    - <<: *p3
+      weight: 0.5
+    - <<: *p4
+      weight: 1.5
diff --git a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc
new file mode 100644
index 0000000..3c981ce
--- /dev/null
+++ b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc
@@ -0,0 +1,144 @@
+/** @file
+
+  nexthop unit test stubs for unit testing nexthop strategies.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  @section details Details
+
+  Implements code necessary for Reverse Proxy which mostly consists of
+  general purpose hostname substitution in URLs.
+
+ */
+
+#include "nexthop_test_stubs.h"
+
+#include "HttpTransact.h"
+
+void
+br(HttpRequestData *h, const char *os_hostname, sockaddr const *dest_ip)
+{
+  HdrHeap *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64);
+  h->hdr        = new HTTPHdr();
+  h->hdr->create(HTTP_TYPE_REQUEST, heap);
+  h->hostname_str = ats_strdup(os_hostname);
+  h->xact_start   = time(nullptr);
+  ink_zero(h->src_ip);
+  ink_zero(h->dest_ip);
+  ats_ip_copy(&h->dest_ip.sa, dest_ip);
+  h->incoming_port = 80;
+  h->api_info      = new HttpApiInfo();
+}
+
+void
+br_reinit(HttpRequestData *h)
+{
+  if (h->hdr) {
+    ats_free(h->hdr);
+    ats_free(h->hostname_str);
+    if (h->hdr) {
+      delete h->hdr;
+    }
+    if (h->api_info) {
+      delete h->api_info;
+    }
+  }
+}
+
+void
+PrintToStdErr(const char *fmt, ...)
+{
+  va_list args;
+  va_start(args, fmt);
+  vfprintf(stderr, fmt, args);
+  va_end(args);
+}
+
+char *
+HttpRequestData::get_string()
+{
+  return nullptr;
+}
+const char *
+HttpRequestData::get_host()
+{
+  return nullptr;
+}
+sockaddr const *
+HttpRequestData::get_ip()
+{
+  return nullptr;
+}
+
+sockaddr const *
+HttpRequestData::get_client_ip()
+{
+  return nullptr;
+}
+
+#include "InkAPIInternal.h"
+void
+ConfigUpdateCbTable::invoke(char const *p)
+{
+}
+
+#include "I_Machine.h"
+
+Machine::Machine(char const *hostname, sockaddr const *addr) {}
+Machine::~Machine() {}
+Machine *
+Machine::instance()
+{
+  return new Machine(nullptr, nullptr);
+}
+bool
+Machine::is_self(const char *name)
+{
+  return false;
+}
+
+#include "HostStatus.h"
+
+HostStatRec::HostStatRec(){};
+HostStatus::HostStatus() {}
+HostStatus::~HostStatus(){};
+HostStatRec *
+HostStatus::getHostStatus(const char *name)
+{
+  // for unit tests only, always return a record with HOST_STATUS_UP
+  static HostStatRec rec;
+  rec.status = HostStatus_t::HOST_STATUS_UP;
+  return &rec;
+}
+void
+HostStatus::setHostStatus(char const *host, HostStatus_t status, unsigned int, unsigned int)
+{
+  NH_Debug("next_hop", "setting host status for '%s' to %s", host, HostStatusNames[status]);
+}
+
+#include "I_UDPConnection.h"
+
+void
+UDPConnection::Release()
+{
+}
+
+#include "P_UDPPacket.h"
+inkcoreapi ClassAllocator<UDPPacketInternal> udpPacketAllocator("udpPacketAllocator");
+// for UDPPacketInternal::free()
diff --git a/proxy/http/remap/unit-tests/nexthop_test_stubs.h b/proxy/http/remap/unit-tests/nexthop_test_stubs.h
new file mode 100644
index 0000000..e879bc2
--- /dev/null
+++ b/proxy/http/remap/unit-tests/nexthop_test_stubs.h
@@ -0,0 +1,85 @@
+/** @file
+
+  unit test stubs header for linking nexthop unit tests.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  @section details Details
+
+  Implements code necessary for Reverse Proxy which mostly consists of
+  general purpose hostname substitution in URLs.
+
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <iostream>
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define NH_Debug(tag, fmt, ...) PrintToStdErr("%s %s:%d:%s() " fmt "\n", tag, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
+#define NH_Error(fmt, ...) PrintToStdErr("%s:%d:%s() " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
+#define NH_Note(fmt, ...) PrintToStdErr("%s:%d:%s() " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
+#define NH_Warn(fmt, ...) PrintToStdErr("%s:%d:%s() " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
+
+void PrintToStdErr(const char *fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#include "ControlMatcher.h"
+struct TestData : public HttpRequestData {
+  std::string hostname;
+  sockaddr client_ip;
+  sockaddr server_ip;
+
+  TestData()
+  {
+    client_ip.sa_family = AF_INET;
+    memset(client_ip.sa_data, 0, sizeof(client_ip.sa_data));
+  }
+  const char *
+  get_host()
+  {
+    return hostname.c_str();
+  }
+  sockaddr const *
+  get_ip()
+  {
+    return &server_ip;
+  }
+  sockaddr const *
+  get_client_ip()
+  {
+    return &client_ip;
+  }
+  char *
+  get_string()
+  {
+    return nullptr;
+  }
+};
diff --git a/proxy/http/remap/unit-tests/round-robin-tests.yaml b/proxy/http/remap/unit-tests/round-robin-tests.yaml
new file mode 100644
index 0000000..f0f1573
--- /dev/null
+++ b/proxy/http/remap/unit-tests/round-robin-tests.yaml
@@ -0,0 +1,207 @@
+# @file
+#
+#  Unit test data round-robin-tests.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# 
+# unit testing strategies for NextHopRoundRobin.
+#
+strategies:
+  - strategy: 'first-live'
+    policy: first_live 
+    groups: 
+      - &g1
+        - host: p1.foo.com
+          hash_string: slsklslsk
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.1.1:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.1.1:443
+          weight: 1.0
+        - host: p2.foo.com
+          hash_string: srskrsrsk
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.1.2:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.1.2:443
+          weight: 1.0
+      - &g2
+        - host: s1.bar.com
+          hash_string: lslalalal
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.2.1:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.2.1:443
+          weight: 1.0
+        - host: s2.bar.com
+          hash_string: alalalalal
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.2.2:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.2.2:443
+          weight: 1.0
+    scheme: http 
+    failover: 
+      ring_mode: exhaust_ring 
+      response_codes: 
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
+  - strategy: 'rr-strict-exhaust-ring'
+    policy: rr_strict 
+    groups: 
+      - &g1
+        - host: p1.foo.com
+          hash_string: slsklslsk
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.1.1:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.1.1:443
+          weight: 1.0
+        - host: p2.foo.com
+          hash_string: srskrsrsk
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.1.2:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.1.2:443
+          weight: 1.0
+      - &g2
+        - host: s1.bar.com
+          hash_string: lslalalal
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.2.1:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.2.1:443
+          weight: 1.0
+        - host: s2.bar.com
+          hash_string: alalalalal
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.2.2:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.2.2:443
+          weight: 1.0
+    scheme: http 
+    failover: 
+      ring_mode: exhaust_ring 
+      response_codes: 
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
+  - strategy: 'rr-ip'
+    policy: rr_ip
+    groups:
+      - &g1
+        - host: p3.foo.com
+          hash_string: slsklslsk
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.1.3:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.1.3:443
+          weight: 1.0
+        - host: p4.foo.com
+          hash_string: srskrsrsk
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.1.4:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.1.4:443
+          weight: 1.0
+    scheme: http 
+    failover: 
+      ring_mode: alternate_ring
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
+  - strategy: 'latched'
+    policy: latched
+    groups:
+      - &g1
+        - host: p3.foo.com
+          hash_string: slsklslsk
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.1.3:80
+            - scheme: https 
+              port: 443
+              health_check_url: https://192.168.1.3:443
+          weight: 1.0
+        - host: p4.foo.com
+          hash_string: srskrsrsk
+          protocol:
+            - scheme: http 
+              port: 80
+              health_check_url: http://192.168.1.4:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.4:443
+          weight: 1.0
+    scheme: http 
+    failover: 
+      ring_mode: alternate_ring
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
+
diff --git a/proxy/http/remap/unit-tests/simple-strategy.yaml b/proxy/http/remap/unit-tests/simple-strategy.yaml
new file mode 100644
index 0000000..8697b1a
--- /dev/null
+++ b/proxy/http/remap/unit-tests/simple-strategy.yaml
@@ -0,0 +1,118 @@
+# @file
+#
+#  Unit test data strategy.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# 
+# unit test simple-strategy.yaml example
+#
+strategies:
+  - strategy: 'strategy-3'
+    policy: rr_ip 
+    groups: 
+      - &g1
+        - host: p1.foo.com
+          hash_string: slsklslsk
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.1.1:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.1:443
+          weight: 1.0
+        - host: p2.foo.com
+          hash_string: srskrsrsk
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.1.2:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.2:443
+          weight: 1.0
+      - &g2
+        - host: s1.bar.com
+          hash_string: lslalalal
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.2.1:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.2.1:443
+          weight: 1.0
+        - host: s2.bar.com
+          hash_string: alalalalal
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.2.2:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.2.2:443
+          weight: 1.0
+    scheme: https 
+    failover: 
+      ring_mode: exhaust_ring 
+      response_codes: 
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
+  - strategy: 'strategy-4'
+    policy: latched
+    groups:
+      - &g1
+        - host: p3.foo.com
+          hash_string: slsklslsk
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.1.3:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.3:443
+          weight: 1.0
+        - host: p4.foo.com
+          hash_string: srskrsrsk
+          protocol:
+            - scheme: http
+              port: 80
+              health_check_url: http://192.168.1.4:80
+            - scheme: https
+              port: 443
+              health_check_url: https://192.168.1.4:443
+          weight: 1.0
+    scheme: http 
+    failover: 
+      ring_mode: alternate_ring
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
+
diff --git a/proxy/http/remap/unit-tests/strategies-dir/01-hosts.yaml b/proxy/http/remap/unit-tests/strategies-dir/01-hosts.yaml
new file mode 100644
index 0000000..f075058
--- /dev/null
+++ b/proxy/http/remap/unit-tests/strategies-dir/01-hosts.yaml
@@ -0,0 +1,60 @@
+# @file
+#
+#  Unit test data hosts.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# For unit testing hosts.yaml
+#
+hosts:
+  - &p1
+    host: 'p1.foo.com'
+    protocol:
+      - scheme: http
+        port: 80
+        health_check_url: 'http://192.168.1.1:80'
+      - scheme: https
+        port: 443
+        health_check_url: 'https://192.168.1.1:443'
+  - &p2
+    host: 'p2.foo.com'
+    protocol:
+      - scheme: http
+        port: 80
+        health_check_url: 'http://192.168.1.2:80'
+  - &p3
+    host: 'p3.foo.com'
+    protocol:
+      - scheme: http
+        port: 8080
+        health_check_url: 'http://192.168.1.3:8080'
+      - scheme: https
+        port:  8443
+        health_check_url: 'https://192.168.1.3:8443'
+  - &p4
+    host: 'p4.foo.com'
+    protocol:
+      - scheme: http 
+        port: 8080
+        health_check_url: 'http://192.168.1.4:8080'
+      - scheme: https
+        port: 8443
+        health_check_url: 'https://192.168.1.4:8443'
diff --git a/proxy/http/remap/unit-tests/strategies-dir/02-groups.yaml b/proxy/http/remap/unit-tests/strategies-dir/02-groups.yaml
new file mode 100644
index 0000000..6d66e98
--- /dev/null
+++ b/proxy/http/remap/unit-tests/strategies-dir/02-groups.yaml
@@ -0,0 +1,37 @@
+# @file
+#
+#  Unit test data hosts.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# For unit testing hosts.yaml
+#
+groups:
+  - &g1
+    - <<: *p1
+      weight: 0.5
+    - <<: *p2
+      weight: 0.5
+  - &g2
+    - <<: *p3
+      weight: 0.5
+    - <<: *p4
+      weight: 0.5
diff --git a/proxy/http/remap/unit-tests/strategies-dir/03-strategies.yaml b/proxy/http/remap/unit-tests/strategies-dir/03-strategies.yaml
new file mode 100644
index 0000000..3bbd670
--- /dev/null
+++ b/proxy/http/remap/unit-tests/strategies-dir/03-strategies.yaml
@@ -0,0 +1,65 @@
+# @file
+#
+#  Unit test data strategy.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# 
+# unit test data.
+#
+strategies:
+  - strategy: 'mid-tier-north'
+    policy: rr_ip
+    go_direct: true
+    parent_is_proxy: false
+    groups:
+      - *g1
+      - *g2
+    scheme: http
+    failover:
+      max_simple_retries: 2
+      ring_mode: exhaust_ring
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check:
+        - passive
+        - active
+  - strategy: 'mid-tier-south'
+    policy: latched
+    go_direct: false
+    parent_is_proxy: false
+    ignore_self_detect: false
+    groups:
+      - *g1
+      - *g2
+    scheme: http 
+    failover: 
+      max_simple_retries: 2
+      ring_mode: alternate_ring
+      response_codes:
+        - 404
+        - 502
+        - 503
+      health_check: 
+        - passive
+        - active
diff --git a/proxy/http/remap/unit-tests/strategy.yaml b/proxy/http/remap/unit-tests/strategy.yaml
new file mode 100644
index 0000000..fb7cc65
--- /dev/null
+++ b/proxy/http/remap/unit-tests/strategy.yaml
@@ -0,0 +1,59 @@
+# @file
+#
+#  Unit test data strategy.yaml file for testing the NextHopStrategyFactory
+#
+#  @section license License
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  @section details Details
+#
+# 
+# unit test strategy.yaml example
+#
+#include unit-tests/hosts.yaml
+#
+strategies:
+  - strategy: 'strategy-1'
+    policy: consistent_hash
+    hash_key: cache_key
+    go_direct: false
+    groups:
+      - *g1
+      - *g2
+    scheme: http
+    failover: 
+      ring_mode: exhaust_ring
+      response_codes:
+        - 404
+        - 503
+      health_check:
+        - passive
+  - strategy: 'strategy-2'
+    policy: rr_strict
+    go_direct: true
+    groups:
+      - *g1
+      - *g2
+    scheme: http
+    failover:
+      ring_mode: exhaust_ring
+      response_codes:
+        - 404
+        - 503
+      health_check:
+        - passive
diff --git a/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc b/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc
new file mode 100644
index 0000000..172e85c
--- /dev/null
+++ b/proxy/http/remap/unit-tests/test_NextHopConsistentHash.cc
@@ -0,0 +1,385 @@
+/** @file
+
+  Unit tests for the NextHopConsistentHash.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  @section details Details
+
+  Unit testing the NextHopConsistentHash class.
+
+ */
+
+#define CATCH_CONFIG_MAIN /* include main function */
+
+#include <catch.hpp> /* catch unit-test framework */
+#include <yaml-cpp/yaml.h>
+
+#include "nexthop_test_stubs.h"
+#include "NextHopSelectionStrategy.h"
+#include "NextHopStrategyFactory.h"
+#include "NextHopConsistentHash.h"
+
+#include "HTTP.h"
+extern int cmd_disable_pfreelist;
+
+SCENARIO("Testing NextHopConsistentHash class, using policy 'consistent_hash'", "[NextHopConsistentHash]")
+{
+  // We need this to build a HdrHeap object in br();
+  // No thread setup, forbid use of thread local allocators.
+  cmd_disable_pfreelist = true;
+  // Get all of the HTTP WKS items populated.
+  http_init();
+
+  GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.")
+  {
+    // load the configuration strtegies.
+    std::shared_ptr<NextHopSelectionStrategy> strategy;
+    NextHopStrategyFactory nhf("unit-tests/consistent-hash-tests.yaml");
+    strategy = nhf.strategyInstance("consistent-hash-1");
+
+    WHEN("the config is loaded.")
+    {
+      THEN("then testing consistent hash.")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(strategy != nullptr);
+        REQUIRE(strategy->groups == 3);
+      }
+    }
+
+    WHEN("requests are received.")
+    {
+      HttpRequestData request;
+      ParentResult result;
+      TestData rdata;
+      rdata.xact_start        = time(nullptr);
+      uint64_t fail_threshold = 1;
+      uint64_t retry_time     = 1;
+
+      // need to run these checks in succession so there
+      // are no host status state changes.
+      //
+      // These tests simulate failed requests using a selected host.
+      // markNextHopDown() is called by the state machine when
+      // there is a request failure due to a connection error or
+      // timeout.  the 'result' struct has the information on the
+      // host used in the failed request and when called, marks the
+      // host indicated in the 'request' struct as unavailable.
+      //
+      // Here we walk through making requests then marking the selected
+      // host down until all are down and the origin is finally chosen.
+      //
+      THEN("when making requests and taking nodes down.")
+      {
+        // first request.
+        br(&request, "rabbit.net");
+        result.reset();
+        strategy->findNextHop(10001, result, request, fail_threshold, retry_time);
+
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+
+        // mark down p1.foo.com.  markNextHopDown looks at the 'result'
+        // and uses the host index there mark down the host selected
+        // from a
+        strategy->markNextHopDown(10001, result, 1, fail_threshold);
+
+        // second request - reusing the ParentResult from the last request
+        // simulating a failure triggers a search for another parent, not firstcall.
+        br(&request, "rabbit.net");
+        strategy->findNextHop(10002, result, request, fail_threshold, retry_time);
+
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "p2.foo.com") == 0);
+
+        // mark down p2.foo.com
+        strategy->markNextHopDown(10002, result, 1, fail_threshold);
+
+        // third request - reusing the ParentResult from the last request
+        // simulating a failure triggers a search for another parent, not firstcall.
+        br(&request, "rabbit.net");
+        strategy->findNextHop(10003, result, request, fail_threshold, retry_time);
+
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "s2.bar.com") == 0);
+
+        // mark down s2.bar.com
+        strategy->markNextHopDown(10003, result, 1, fail_threshold);
+
+        // fourth request - reusing the ParentResult from the last request
+        // simulating a failure triggers a search for another parent, not firstcall.
+        br(&request, "rabbit.net");
+        strategy->findNextHop(10004, result, request, fail_threshold, retry_time);
+
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "s1.bar.com") == 0);
+
+        // mark down s1.bar.com.
+        strategy->markNextHopDown(10004, result, 1, fail_threshold);
+
+        // fifth request - reusing the ParentResult from the last request
+        // simulating a failure triggers a search for another parent, not firstcall.
+        br(&request, "rabbit.net");
+        strategy->findNextHop(10005, result, request, fail_threshold, retry_time);
+
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "q1.bar.com") == 0);
+
+        // mark down q1.bar.com
+        strategy->markNextHopDown(10005, result, 1, fail_threshold);
+        // sixth request - reusing the ParentResult from the last request
+        // simulating a failure triggers a search for another parent, not firstcall.
+        br(&request, "rabbit.net");
+        strategy->findNextHop(10006, result, request, fail_threshold, retry_time);
+
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "q2.bar.com") == 0);
+
+        // mark down q2.bar.com
+        strategy->markNextHopDown(10006, result, 1, fail_threshold);
+        // seventh request - reusing the ParentResult from the last request
+        // simulating a failure triggers a search for another parent, not firstcall.
+        br(&request, "rabbit.net");
+        strategy->findNextHop(10007, result, request, fail_threshold, retry_time);
+
+        CHECK(result.result == ParentResultType::PARENT_DIRECT);
+        CHECK(result.hostname == nullptr);
+
+        // sleep and test that q2 is becomes retryable;
+        time_t now = time(nullptr) + 5;
+
+        // eighth request - reusing the ParentResult from the last request
+        // simulating a failure triggers a search for another parent, not firstcall.
+        br(&request, "rabbit.net");
+        strategy->findNextHop(10008, result, request, fail_threshold, retry_time, now);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "q2.bar.com") == 0);
+      }
+    }
+  }
+}
+
+SCENARIO("Testing NextHopConsistentHash class (all firstcalls), using policy 'consistent_hash'", "[NextHopConsistentHash]")
+{
+  // We need this to build a HdrHeap object in br();
+  // No thread setup, forbid use of thread local allocators.
+  cmd_disable_pfreelist = true;
+  // Get all of the HTTP WKS items populated.
+  http_init();
+
+  GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.")
+  {
+    std::shared_ptr<NextHopSelectionStrategy> strategy;
+    NextHopStrategyFactory nhf("unit-tests/consistent-hash-tests.yaml");
+    strategy = nhf.strategyInstance("consistent-hash-1");
+
+    WHEN("the config is loaded.")
+    {
+      THEN("then testing consistent hash.")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(strategy != nullptr);
+        REQUIRE(strategy->groups == 3);
+      }
+    }
+
+    // Same test procedure as the first scenario but we clear the 'result' struct
+    // so that we are making initial requests and simulating that hosts were
+    // removed by different transactions.
+    //
+    // these checks need to be run in sequence so that there are no host status
+    // state changes induced by using multiple WHEN() and THEN()
+    WHEN("initial requests are made and hosts are unavailable .")
+    {
+      uint64_t fail_threshold = 1;
+      uint64_t retry_time     = 1;
+      TestData rdata;
+      rdata.xact_start = time(nullptr);
+      HttpRequestData request;
+      ParentResult result;
+
+      THEN("when making requests and taking nodes down.")
+      {
+        // first request.
+        br(&request, "rabbit.net");
+        result.reset();
+        strategy->findNextHop(20001, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+
+        // mark down p1.foo.com
+        strategy->markNextHopDown(20001, result, 1, fail_threshold);
+        // second request
+        br(&request, "rabbit.net");
+        result.reset();
+        strategy->findNextHop(20002, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "p2.foo.com") == 0);
+
+        // mark down p2.foo.com
+        strategy->markNextHopDown(20002, result, 1, fail_threshold);
+
+        // third request
+        br(&request, "rabbit.net");
+        result.reset();
+        strategy->findNextHop(20003, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "s2.bar.com") == 0);
+
+        // mark down s2.bar.com
+        strategy->markNextHopDown(20003, result, 1, fail_threshold);
+
+        // fourth request
+        br(&request, "rabbit.net");
+        result.reset();
+        strategy->findNextHop(20004, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "q1.bar.com") == 0);
+
+        // mark down q1.bar.com
+        strategy->markNextHopDown(20004, result, 1, fail_threshold);
+
+        // fifth request
+        br(&request, "rabbit.net/asset1");
+        result.reset();
+        strategy->findNextHop(20005, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_DIRECT);
+        CHECK(result.hostname == nullptr);
+
+        // sixth request - wait and p1 should now become available
+        time_t now = time(nullptr) + 5;
+        br(&request, "rabbit.net");
+        result.reset();
+        strategy->findNextHop(20006, result, request, fail_threshold, retry_time, now);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+      }
+    }
+  }
+}
+
+SCENARIO("Testing NextHopConsistentHash class (alternating rings), using policy 'consistent_hash'", "[NextHopConsistentHash]")
+{
+  // We need this to build a HdrHeap object in br();
+  // No thread setup, forbid use of thread local allocators.
+  cmd_disable_pfreelist = true;
+  // Get all of the HTTP WKS items populated.
+  http_init();
+
+  GIVEN("Loading the consistent-hash-tests.yaml config for 'consistent_hash' tests.")
+  {
+    std::shared_ptr<NextHopSelectionStrategy> strategy;
+    NextHopStrategyFactory nhf("unit-tests/consistent-hash-tests.yaml");
+    strategy = nhf.strategyInstance("consistent-hash-2");
+
+    WHEN("the config is loaded.")
+    {
+      THEN("then testing consistent hash.")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(strategy != nullptr);
+        REQUIRE(strategy->groups == 3);
+      }
+    }
+
+    // makeing requests and marking down hosts with a config set for alternating ring mode.
+    WHEN("requests are made in a config set for alternating rings and hosts are marked down.")
+    {
+      uint64_t fail_threshold = 1;
+      uint64_t retry_time     = 1;
+      TestData rdata;
+      rdata.xact_start = time(nullptr);
+      HttpRequestData request;
+      ParentResult result;
+
+      THEN("expect the following results when making requests and marking hosts down.")
+      {
+        // first request.
+        br(&request, "bunny.net/asset1");
+        result.reset();
+        strategy->findNextHop(30001, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "c2.foo.com") == 0);
+
+        // simulated failure, mark c2 down and retry request
+        strategy->markNextHopDown(30001, result, 1, fail_threshold);
+
+        // second request
+        br(&request, "bunny.net.net/asset1");
+        strategy->findNextHop(30002, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "c3.bar.com") == 0);
+
+        // mark down c3.bar.com
+        strategy->markNextHopDown(30002, result, 1, fail_threshold);
+
+        // third request
+        br(&request, "bunny.net/asset2");
+        result.reset();
+        strategy->findNextHop(30003, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "c6.bar.com") == 0);
+
+        // just mark it down and retry request
+        strategy->markNextHopDown(30003, result, 1, fail_threshold);
+        // fourth request
+        br(&request, "bunny.net/asset2");
+        strategy->findNextHop(30004, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "c1.foo.com") == 0);
+
+        // mark it down
+        strategy->markNextHopDown(30004, result, 1, fail_threshold);
+        // fifth request - new request
+        br(&request, "bunny.net/asset3");
+        result.reset();
+        strategy->findNextHop(30005, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "c4.bar.com") == 0);
+
+        // mark it down and retry
+        strategy->markNextHopDown(30005, result, 1, fail_threshold);
+        // sixth request
+        br(&request, "bunny.net/asset3");
+        result.reset();
+        strategy->findNextHop(30006, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "c5.bar.com") == 0);
+
+        // mark it down
+        strategy->markNextHopDown(30006, result, 1, fail_threshold);
+        // seventh request - new request with all hosts down and go_direct is false.
+        br(&request, "bunny.net/asset4");
+        result.reset();
+        strategy->findNextHop(30007, result, request, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_FAIL);
+        CHECK(result.hostname == nullptr);
+
+        // eighth request - retry after waiting for the retry window to expire.
+        time_t now = time(nullptr) + 5;
+        br(&request, "bunny.net/asset4");
+        result.reset();
+        strategy->findNextHop(30008, result, request, fail_threshold, retry_time, now);
+        CHECK(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "c2.foo.com") == 0);
+      }
+    }
+  }
+}
diff --git a/proxy/http/remap/unit-tests/test_NextHopRoundRobin.cc b/proxy/http/remap/unit-tests/test_NextHopRoundRobin.cc
new file mode 100644
index 0000000..104efc0
--- /dev/null
+++ b/proxy/http/remap/unit-tests/test_NextHopRoundRobin.cc
@@ -0,0 +1,306 @@
+/** @file
+
+  Unit tests for the NextHopRoundRobin.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  @section details Details
+
+  Unit testing the NextHopRoundRobin class.
+
+ */
+
+#define CATCH_CONFIG_MAIN /* include main function */
+
+#include <catch.hpp> /* catch unit-test framework */
+#include <yaml-cpp/yaml.h>
+
+#include "nexthop_test_stubs.h"
+#include "NextHopSelectionStrategy.h"
+#include "NextHopStrategyFactory.h"
+#include "NextHopRoundRobin.h"
+
+SCENARIO("Testing NextHopRoundRobin class, using policy 'rr-strict'", "[NextHopRoundRobin]")
+{
+  GIVEN("Loading the round-robin-tests.yaml config for round robin 'rr-strict' tests.")
+  {
+    std::shared_ptr<NextHopSelectionStrategy> strategy;
+    NextHopStrategyFactory nhf("unit-tests/round-robin-tests.yaml");
+    strategy = nhf.strategyInstance("rr-strict-exhaust-ring");
+
+    WHEN("the config is loaded.")
+    {
+      THEN("the rr-strict strategy is ready for use.")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(strategy != nullptr);
+        REQUIRE(strategy->policy_type == NH_RR_STRICT);
+      }
+    }
+
+    WHEN("making requests using a 'rr-strict' policy.")
+    {
+      uint64_t fail_threshold = 1;
+      uint64_t retry_time     = 1;
+      TestData rdata;
+
+      THEN("then testing rr-strict.")
+      {
+        // first request.
+        ParentResult result;
+        strategy->findNextHop(10000, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+
+        // second request.
+        result.reset();
+        strategy->findNextHop(10001, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p2.foo.com") == 0);
+
+        // third request.
+        result.reset();
+        strategy->findNextHop(10002, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+
+        // did not reset result, kept it as last parent selected was p1.fo.com, mark it down and we should only select p2.foo.com
+        strategy->markNextHopDown(10003, result, 1, fail_threshold);
+
+        // fourth request, p1 is down should select p2.
+        result.reset();
+        strategy->findNextHop(10004, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p2.foo.com") == 0);
+
+        // fifth request, p1 is down should still select p2.
+        result.reset();
+        strategy->findNextHop(10005, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p2.foo.com") == 0);
+
+        // mark down p2.
+        strategy->markNextHopDown(10006, result, 1, fail_threshold);
+
+        // fifth request, p1 and p2 are both down, should get s1.bar.com from failover ring.
+        result.reset();
+        strategy->findNextHop(10007, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "s1.bar.com") == 0);
+
+        // sixth request, p1 and p2 are still down, should get s1.bar.com from failover ring.
+        result.reset();
+        strategy->findNextHop(10008, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "s1.bar.com") == 0);
+
+        // mark down s1.
+        strategy->markNextHopDown(10009, result, 1, fail_threshold);
+
+        // seventh request, p1, p2, s1 are down, should get s2.bar.com from failover ring.
+        result.reset();
+        strategy->findNextHop(10010, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "s2.bar.com") == 0);
+
+        // mark down s2.
+        strategy->markNextHopDown(10011, result, 1, fail_threshold);
+
+        // eighth request, p1, p2, s1, s2 are down, should get PARENT_DIRECT as go_direct is true
+        result.reset();
+        strategy->findNextHop(10012, result, rdata, fail_threshold, retry_time);
+        CHECK(result.result == ParentResultType::PARENT_DIRECT);
+
+        // change the request time to trigger a retry.
+        time_t now = (time(nullptr) + 5);
+
+        // ninth request, p1 and p2 are still down, should get p2.foo.com as it will be retried
+        result.reset();
+        strategy->findNextHop(10013, result, rdata, fail_threshold, retry_time, now);
+        REQUIRE(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "p2.foo.com") == 0);
+
+        // tenth request, p1 should now be retried.
+        result.reset();
+        strategy->findNextHop(10014, result, rdata, fail_threshold, retry_time, now);
+        REQUIRE(result.result == ParentResultType::PARENT_SPECIFIED);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+      }
+    }
+  }
+}
+
+SCENARIO("Testing NextHopRoundRobin class, using policy 'first-live'", "[NextHopRoundRobin]")
+{
+  GIVEN("Loading the round-robin-tests.yaml config for round robin 'first-live' tests.")
+  {
+    std::shared_ptr<NextHopSelectionStrategy> strategy;
+    NextHopStrategyFactory nhf("unit-tests/round-robin-tests.yaml");
+    strategy = nhf.strategyInstance("first-live");
+
+    WHEN("the config is loaded.")
+    {
+      THEN("the 'first-live' strategy is available.")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(strategy != nullptr);
+        REQUIRE(strategy->policy_type == NH_FIRST_LIVE);
+      }
+    }
+
+    WHEN("when using a strategy with a 'first-live' policy.")
+    {
+      uint64_t fail_threshold = 1;
+      uint64_t retry_time     = 1;
+      TestData rdata;
+
+      THEN("when making requests and marking down hosts.")
+      {
+        // first request.
+        ParentResult result;
+        strategy->findNextHop(20000, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+
+        // second request.
+        result.reset();
+        strategy->findNextHop(20001, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+
+        // mark down p1.
+        strategy->markNextHopDown(20002, result, 1, fail_threshold);
+
+        // third request.
+        result.reset();
+        strategy->findNextHop(20003, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p2.foo.com") == 0);
+
+        // change the request time to trigger a retry.
+        time_t now = (time(nullptr) + 5);
+
+        // fourth request, p1 should be marked for retry
+        result.reset();
+        strategy->findNextHop(20004, result, rdata, fail_threshold, retry_time, now);
+        CHECK(strcmp(result.hostname, "p1.foo.com") == 0);
+      }
+    }
+  }
+}
+
+SCENARIO("Testing NextHopRoundRobin class, using policy 'rr-ip'", "[NextHopRoundRobin]")
+{
+  GIVEN("Loading the round-robin-tests.yaml config for round robin 'rr-ip' tests.")
+  {
+    std::shared_ptr<NextHopSelectionStrategy> strategy;
+    NextHopStrategyFactory nhf("unit-tests/round-robin-tests.yaml");
+    strategy = nhf.strategyInstance("rr-ip");
+    sockaddr_in sa1, sa2;
+    sa1.sin_port   = 10000;
+    sa1.sin_family = AF_INET;
+    inet_pton(AF_INET, "192.168.1.1", &(sa1.sin_addr));
+    sa2.sin_port   = 10001;
+    sa2.sin_family = AF_INET;
+    inet_pton(AF_INET, "192.168.1.2", &(sa2.sin_addr));
+    WHEN("the config is loaded.")
+    {
+      THEN("then the 'rr-strict' strategy is ready.")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(strategy != nullptr);
+        REQUIRE(strategy->policy_type == NH_RR_IP);
+      }
+    }
+
+    WHEN("using the 'rr-stric' strategy.")
+    {
+      uint64_t fail_threshold = 1;
+      uint64_t retry_time     = 1;
+      TestData rdata;
+
+      THEN("when making requests and marking down hosts.")
+      {
+        // first request.
+        memcpy(&rdata.client_ip, &sa1, sizeof(sa1));
+        ParentResult result;
+        strategy->findNextHop(30000, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p4.foo.com") == 0);
+
+        // second request.
+        memcpy(&rdata.client_ip, &sa2, sizeof(sa2));
+        result.reset();
+        strategy->findNextHop(30001, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p3.foo.com") == 0);
+
+        // third  request with same client ip, result should still be p3
+        result.reset();
+        strategy->findNextHop(30002, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p3.foo.com") == 0);
+
+        // fourth  request with same client ip and same result indicating a failure should result in p4
+        // being selected.
+        strategy->findNextHop(30003, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p4.foo.com") == 0);
+      }
+    }
+  }
+}
+
+SCENARIO("Testing NextHopRoundRobin class, using policy 'latched'", "[NextHopRoundRobin]")
+{
+  GIVEN("Loading the round-robin-tests.yaml config for round robin 'latched' tests.")
+  {
+    std::shared_ptr<NextHopSelectionStrategy> strategy;
+    NextHopStrategyFactory nhf("unit-tests/round-robin-tests.yaml");
+    strategy = nhf.strategyInstance("latched");
+
+    WHEN("the config is loaded.")
+    {
+      THEN("then the 'latched' strategy is available.")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(strategy != nullptr);
+        REQUIRE(strategy->policy_type == NH_RR_LATCHED);
+      }
+    }
+
+    WHEN("using a strategy having a 'latched' policy.")
+    {
+      uint64_t fail_threshold = 1;
+      uint64_t retry_time     = 1;
+      TestData rdata;
+
+      THEN("when making requests and marking down hosts.")
+      {
+        // first request should select p3
+        ParentResult result;
+        strategy->findNextHop(40000, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p3.foo.com") == 0);
+
+        // second request should select p3
+        result.reset();
+        strategy->findNextHop(40001, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p3.foo.com") == 0);
+
+        // third request, use previous result to simulate a failure, we should now select p4.
+        strategy->findNextHop(40002, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p4.foo.com") == 0);
+
+        // fourth request we should be latched on p4
+        result.reset();
+        strategy->findNextHop(40003, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p4.foo.com") == 0);
+
+        // fifth request, use previous result to simulate a failure, we should now select p3.
+        strategy->findNextHop(40004, result, rdata, fail_threshold, retry_time);
+        CHECK(strcmp(result.hostname, "p3.foo.com") == 0);
+      }
+    }
+  }
+}
diff --git a/proxy/http/remap/unit-tests/test_NextHopStrategyFactory.cc b/proxy/http/remap/unit-tests/test_NextHopStrategyFactory.cc
new file mode 100644
index 0000000..2bfc260
--- /dev/null
+++ b/proxy/http/remap/unit-tests/test_NextHopStrategyFactory.cc
@@ -0,0 +1,953 @@
+/** @file
+
+  Unit tests for the NextHopStrategyFactory.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  @section details Details
+
+  Unit testing the NextHopStrategy factory.
+
+ */
+
+#define CATCH_CONFIG_MAIN /* include main function */
+#include <catch.hpp>      /* catch unit-test framework */
+#include <fstream>        /* ofstream */
+#include <memory>
+#include <utime.h>
+#include <yaml-cpp/yaml.h>
+
+#include "nexthop_test_stubs.h"
+#include "NextHopSelectionStrategy.h"
+#include "NextHopStrategyFactory.h"
+#include "NextHopConsistentHash.h"
+#include "NextHopRoundRobin.h"
+
+SCENARIO("factory tests loading yaml configs", "[loadConfig]")
+{
+  GIVEN("Loading the strategy.yaml with included 'hosts.yaml'.")
+  {
+    NextHopStrategyFactory nhf("unit-tests/strategy.yaml");
+
+    WHEN("the two files are loaded.")
+    {
+      THEN("there are two strategies defined in the config, 'strategy-1' and 'strategy-2'")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(nhf.strategyInstance("strategy-1") != nullptr);
+        REQUIRE(nhf.strategyInstance("strategy-2") != nullptr);
+        REQUIRE(nhf.strategyInstance("notthere") == nullptr);
+      }
+    }
+
+    WHEN("'strategy-1' details are checked.")
+    {
+      THEN("Expect that these results for 'strategy-1'")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("strategy-1");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->parent_is_proxy == true);
+        CHECK(strategy->max_simple_retries == 1);
+        CHECK(strategy->policy_type == NH_CONSISTENT_HASH);
+
+        // 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());
+        REQUIRE(ptr != nullptr);
+        CHECK(ptr->hash_key == NH_CACHE_HASH_KEY);
+
+        CHECK(strategy->go_direct == false);
+        CHECK(strategy->scheme == NH_SCHEME_HTTP);
+        CHECK(strategy->ring_mode == NH_EXHAUST_RING);
+        CHECK(strategy->groups == 2);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 1.5);
+                CHECK(h->available == true);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 1.5);
+                CHECK(h->available == true);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p3.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.3:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.3:8443");
+                CHECK(h->weight == 0.5);
+                CHECK(h->available == true);
+                break;
+              case 1:
+                CHECK(h->hostname == "p4.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.4:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.4:8443");
+                CHECK(h->weight == 1.5);
+                CHECK(h->available == true);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+
+    WHEN("'strategy-2' details are checked.")
+    {
+      THEN("Expect that these results for 'strategy-2'")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("strategy-2");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->policy_type == NH_RR_STRICT);
+        CHECK(strategy->go_direct == true);
+        CHECK(strategy->scheme == NH_SCHEME_HTTP);
+        CHECK(strategy->ring_mode == NH_EXHAUST_RING);
+        CHECK(strategy->groups == 2);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 1.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 1.5);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p3.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.3:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.3:8443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p4.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.4:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.4:8443");
+                CHECK(h->weight == 1.5);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+  }
+
+  GIVEN("loading a yaml config, simple-strategy.yaml ")
+  {
+    NextHopStrategyFactory nhf("unit-tests/simple-strategy.yaml");
+
+    WHEN("loading the single file")
+    {
+      THEN("loading the simple-strategy.yaml")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(nhf.strategyInstance("strategy-3") != nullptr);
+        REQUIRE(nhf.strategyInstance("strategy-4") != nullptr);
+      }
+    }
+
+    WHEN("'strategy-3' details are checked.")
+    {
+      THEN("Expect that these results for 'strategy-3'")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("strategy-3");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->policy_type == NH_RR_IP);
+        CHECK(strategy->go_direct == true);
+        CHECK(strategy->scheme == NH_SCHEME_HTTPS);
+        CHECK(strategy->ring_mode == NH_EXHAUST_RING);
+        CHECK(strategy->groups == 2);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 1.0);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.2:443");
+                CHECK(h->weight == 1.0);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "s1.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.1:443");
+                CHECK(h->weight == 1.0);
+                break;
+              case 1:
+                CHECK(h->hostname == "s2.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.2:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.2:443");
+                CHECK(h->weight == 1.0);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+
+    WHEN("'strategy-4' details are checked.")
+    {
+      THEN("Expect that these results for 'strategy-4'")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("strategy-4");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->policy_type == NH_RR_LATCHED);
+        CHECK(strategy->go_direct == true);
+        CHECK(strategy->scheme == NH_SCHEME_HTTP);
+        CHECK(strategy->ring_mode == NH_ALTERNATE_RING);
+        CHECK(strategy->groups == 1);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p3.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.3:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.3:443");
+                CHECK(h->weight == 1.0);
+                break;
+              case 1:
+                CHECK(h->hostname == "p4.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.4:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.4:443");
+                CHECK(h->weight == 1.0);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+  }
+
+  GIVEN("loading a yaml config combining hosts and strategies into one file, combined.yaml")
+  {
+    NextHopStrategyFactory nhf("unit-tests/combined.yaml");
+
+    WHEN("loading the single file")
+    {
+      THEN("loading the single file when there is only one strategy, 'mid-tier-east'")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(nhf.strategyInstance("mid-tier-east") != nullptr);
+        REQUIRE(nhf.strategyInstance("notthere") == nullptr);
+      }
+    }
+
+    WHEN("the strategy 'mid-tier-north' details are checked.")
+    {
+      THEN("expect the following details.")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("mid-tier-north");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->parent_is_proxy == false);
+        CHECK(strategy->max_simple_retries == 2);
+        CHECK(strategy->policy_type == NH_RR_IP);
+        CHECK(strategy->go_direct == true);
+        CHECK(strategy->scheme == NH_SCHEME_HTTP);
+        CHECK(strategy->ring_mode == NH_EXHAUST_RING);
+        CHECK(strategy->groups == 2);
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(502));
+        CHECK(!strategy->resp_codes.contains(604));
+        CHECK(strategy->health_checks.active == true);
+        CHECK(strategy->health_checks.passive == true);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "s1.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.1:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.1:8443");
+                CHECK(h->weight == 2.0);
+                break;
+              case 1:
+                CHECK(h->hostname == "s2.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.2:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.2:8443");
+                CHECK(h->weight == 1.0);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+
+    WHEN("the strategy 'mid-tier-south' details are checked.")
+    {
+      THEN("expect the following results.")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("mid-tier-south");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->policy_type == NH_RR_LATCHED);
+        CHECK(strategy->parent_is_proxy == false);
+        CHECK(strategy->ignore_self_detect == false);
+        CHECK(strategy->max_simple_retries == 2);
+        CHECK(strategy->go_direct == false);
+        CHECK(strategy->scheme == NH_SCHEME_HTTP);
+        CHECK(strategy->ring_mode == NH_ALTERNATE_RING);
+        CHECK(strategy->groups == 2);
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(502));
+        CHECK(!strategy->resp_codes.contains(604));
+        CHECK(strategy->health_checks.active == true);
+        CHECK(strategy->health_checks.passive == true);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "s1.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.1:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.1:8443");
+                CHECK(h->weight == 2.0);
+                break;
+              case 1:
+                CHECK(h->hostname == "s2.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.2:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.2:8443");
+                CHECK(h->weight == 1.0);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+
+    WHEN("the strategy 'mid-tier-east' details are checked.")
+    {
+      THEN("expect the following results.")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("mid-tier-east");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->policy_type == NH_FIRST_LIVE);
+        CHECK(strategy->parent_is_proxy == false);
+        CHECK(strategy->ignore_self_detect == true);
+        CHECK(strategy->max_simple_retries == 2);
+        CHECK(strategy->go_direct == false);
+        CHECK(strategy->scheme == NH_SCHEME_HTTPS);
+        CHECK(strategy->ring_mode == NH_ALTERNATE_RING);
+        CHECK(strategy->groups == 2);
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(502));
+        CHECK(!strategy->resp_codes.contains(604));
+        CHECK(strategy->health_checks.active == false);
+        CHECK(strategy->health_checks.passive == true);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "s1.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.1:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.1:8443");
+                CHECK(h->weight == 2.0);
+                break;
+              case 1:
+                CHECK(h->hostname == "s2.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.2:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.2:8443");
+                CHECK(h->weight == 1.0);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+
+    WHEN("the strategy 'mid-tier-west' details are checked.")
+    {
+      THEN("expect the following results.")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("mid-tier-west");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->policy_type == NH_RR_STRICT);
+        CHECK(strategy->go_direct == true);
+        CHECK(strategy->scheme == NH_SCHEME_HTTPS);
+        CHECK(strategy->parent_is_proxy == false);
+        CHECK(strategy->max_simple_retries == 2);
+        CHECK(strategy->ring_mode == NH_EXHAUST_RING);
+        CHECK(strategy->groups == 2);
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(502));
+        CHECK(!strategy->resp_codes.contains(604));
+        CHECK(strategy->health_checks.active == true);
+        CHECK(strategy->health_checks.passive == false);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "s1.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.1:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.1:8443");
+                CHECK(h->weight == 2.0);
+                break;
+              case 1:
+                CHECK(h->hostname == "s2.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.2:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.2:8443");
+                CHECK(h->weight == 1.0);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+
+    WHEN("the strategy 'mid-tier-midwest' details are checked.")
+    {
+      THEN("expect the following results.")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("mid-tier-midwest");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->policy_type == NH_CONSISTENT_HASH);
+        CHECK(strategy->parent_is_proxy == false);
+        CHECK(strategy->max_simple_retries == 2);
+
+        // 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());
+        REQUIRE(ptr != nullptr);
+        CHECK(ptr->hash_key == NH_CACHE_HASH_KEY);
+
+        CHECK(strategy->go_direct == true);
+        CHECK(strategy->scheme == NH_SCHEME_HTTPS);
+        CHECK(strategy->ring_mode == NH_EXHAUST_RING);
+        CHECK(strategy->groups == 2);
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(502));
+        CHECK(!strategy->resp_codes.contains(604));
+        CHECK(strategy->health_checks.active == true);
+        CHECK(strategy->health_checks.passive == false);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "s1.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.1:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.1:8443");
+                CHECK(h->weight == 2.0);
+                break;
+              case 1:
+                CHECK(h->hostname == "s2.bar.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.2.2:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.2.2:8443");
+                CHECK(h->weight == 1.0);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+  }
+}
+
+SCENARIO("factory tests loading yaml configs from a directory", "[loadConfig]")
+{
+  GIVEN("Loading the strategies using a directory of 'yaml' files")
+  {
+    NextHopStrategyFactory nhf("unit-tests/strategies-dir");
+
+    WHEN("the two files are loaded.")
+    {
+      THEN("there are two strategies defined in the config, 'strategy-1' and 'strategy-2'")
+      {
+        REQUIRE(nhf.strategies_loaded == true);
+        REQUIRE(nhf.strategyInstance("mid-tier-north") != nullptr);
+        REQUIRE(nhf.strategyInstance("mid-tier-south") != nullptr);
+      }
+    }
+
+    WHEN("the strategy 'mid-tier-north' details are checked.")
+    {
+      THEN("expect the following results.")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("mid-tier-north");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->parent_is_proxy == false);
+        CHECK(strategy->max_simple_retries == 2);
+        CHECK(strategy->policy_type == NH_RR_IP);
+        CHECK(strategy->go_direct == true);
+        CHECK(strategy->scheme == NH_SCHEME_HTTP);
+        CHECK(strategy->ring_mode == NH_EXHAUST_RING);
+        CHECK(strategy->groups == 2);
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(502));
+        CHECK(!strategy->resp_codes.contains(604));
+        CHECK(strategy->health_checks.active == true);
+        CHECK(strategy->health_checks.passive == true);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p3.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.3:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.3:8443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p4.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.4:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.4:8443");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+
+    WHEN("the strategy 'mid-tier-south' details are checked.")
+    {
+      THEN("expect the following results.")
+      {
+        std::shared_ptr<NextHopSelectionStrategy> strategy = nhf.strategyInstance("mid-tier-south");
+        REQUIRE(strategy != nullptr);
+        CHECK(strategy->policy_type == NH_RR_LATCHED);
+        CHECK(strategy->parent_is_proxy == false);
+        CHECK(strategy->ignore_self_detect == false);
+        CHECK(strategy->max_simple_retries == 2);
+        CHECK(strategy->go_direct == false);
+        CHECK(strategy->scheme == NH_SCHEME_HTTP);
+        CHECK(strategy->ring_mode == NH_ALTERNATE_RING);
+        CHECK(strategy->groups == 2);
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(502));
+        CHECK(!strategy->resp_codes.contains(604));
+        CHECK(strategy->health_checks.active == true);
+        CHECK(strategy->health_checks.passive == true);
+        std::shared_ptr<HostRecord> h = strategy->host_groups[0][0];
+        CHECK(h != nullptr);
+        for (unsigned int i = 0; i < strategy->groups; i++) {
+          CHECK(strategy->host_groups[i].size() == 2);
+          for (unsigned int j = 0; j < strategy->host_groups[i].size(); j++) {
+            h = strategy->host_groups[i][j];
+            switch (i) {
+            case 0:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p1.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.1:80");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.1:443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p2.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 80);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.2:80");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            case 1:
+              switch (j) {
+              case 0:
+                CHECK(h->hostname == "p3.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.3:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.3:8443");
+                CHECK(h->weight == 0.5);
+                break;
+              case 1:
+                CHECK(h->hostname == "p4.foo.com");
+                CHECK(h->protocols[0]->scheme == NH_SCHEME_HTTP);
+                CHECK(h->protocols[0]->port == 8080);
+                CHECK(h->protocols[0]->health_check_url == "http://192.168.1.4:8080");
+                CHECK(h->protocols[1]->scheme == NH_SCHEME_HTTPS);
+                CHECK(h->protocols[1]->port == 8443);
+                CHECK(h->protocols[1]->health_check_url == "https://192.168.1.4:8443");
+                CHECK(h->weight == 0.5);
+                break;
+              }
+              break;
+            }
+          }
+        }
+        CHECK(strategy->resp_codes.contains(404));
+        CHECK(strategy->resp_codes.contains(503));
+        CHECK(!strategy->resp_codes.contains(604));
+      }
+    }
+  }
+}