You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2022/07/28 20:05:37 UTC

[trafficcontrol] branch master updated: T3c add parent retry params for first./inner./last. (#6859)

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

rob pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 33feb76f10 T3c add parent retry params for first./inner./last. (#6859)
33feb76f10 is described below

commit 33feb76f1040fab09d7893772b95e8a0b010955a
Author: Brian Olsen <br...@comcast.com>
AuthorDate: Thu Jul 28 14:05:31 2022 -0600

    T3c add parent retry params for first./inner./last. (#6859)
    
    * Add support for first./inner./last. for parent.config retries
    
    * move merge_parent_groups from mso table into parameters list in another section
    
    * add documentation for first./inner./last. retry/markdown options
    
    * refactor how parent retry config keys are made
    
    * fix serverPlacement logic for non topology ds, add godoc function comment
    
    * remove extra secondary_mode check and add hasValues check for IsLastCacheTier
    
    * snidotyaml_test: convert to using ParentConfigRetryKeysDefault for Algorithm
---
 CHANGELOG.md                                 |   1 +
 docs/source/admin/quick_howto/multi_site.rst |  44 +-
 docs/source/overview/delivery_services.rst   |  60 +-
 lib/go-atscfg/parentdotconfig.go             | 748 ++++++++++++-----------
 lib/go-atscfg/parentdotconfig_test.go        | 849 +++++++++++++++++++++++++--
 lib/go-atscfg/snidotyaml_test.go             |   8 +-
 lib/go-atscfg/sslservernamedotyaml_test.go   |   8 +-
 lib/go-atscfg/strategiesdotconfig.go         |   7 +-
 lib/go-atscfg/strategiesdotconfig_test.go    | 755 ++++++++++++++++++++++--
 9 files changed, 1992 insertions(+), 488 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ac611ea77..8fec71c9e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Change to t3c regex_revalidate so that STALE is no longer explicitly added for default revalidate rule for ATS version backwards compatibility.
 - Change to t3c diff to flag a config file for replacement if owner/group settings are not `ats` [#6879](https://github.com/apache/trafficcontrol/issues/6879).
 - t3c now looks in the executable dir path for t3c- utilities
+- Added support for parent.config markdown/retry DS parameters using first./inner./last. prefixes.  mso. and <null> prefixes should be deprecated.
 
 ### Fixed
 - Fixed TO to default route ID to 0, if it is not present in the request context.
diff --git a/docs/source/admin/quick_howto/multi_site.rst b/docs/source/admin/quick_howto/multi_site.rst
index 2b648182bf..5e64937ec8 100644
--- a/docs/source/admin/quick_howto/multi_site.rst
+++ b/docs/source/admin/quick_howto/multi_site.rst
@@ -68,33 +68,35 @@ The following steps will take you through the procedure of setting up an :abbr:`
 
 	#) Click :guilabel:`Show profile parameters` to bring up the :term:`Parameters` screen for the :term:`Profile`. Create the following :term:`Parameters`:
 
-		+----------------------------------------+------------------+--------------------------+-------------------------+
-		| Parameter Name                         | Config File Name | Value                    | ATS parent.config value |
-		+========================================+==================+==========================+=========================+
-		| mso.algorithm                          | parent.config    | true, false, strict,     | round_robin             |
-		|                                        |                  | consistent_hash          |                         |
-		+----------------------------------------+------------------+--------------------------+-------------------------+
-		| mso.parent_retry                       | parent.config    | simple_retry, both,      | parent_retry            |
-		|                                        |                  | unavailable_server_retry |                         |
-		+----------------------------------------+------------------+--------------------------+-------------------------+
-		| mso.unavailable_server_retry_responses | parent.config    | list of server response  | defaults to the value   |
-		|                                        |                  | codes, eg "500,502,503"  | in records.config       |
-		|                                        |                  |                          | when unused.            |
-		+----------------------------------------+------------------+--------------------------+-------------------------+
-		| mso.max_simple_retries                 | parent.config    | Nubmer of retries made   | defaults to the value   |
-		|                                        |                  | after a 4xx error        | in records.config       |
-		|                                        |                  |                          | when unused.            |
-		+----------------------------------------+------------------+--------------------------+-------------------------+
-		| mso.max_unavailable_server_retries     | parent.config    | Nubmer of retries made   | defaults to the value   |
-		|                                        |                  | after a 5xx error        | in records.config       |
-		|                                        |                  |                          | when unused.            |
-		+----------------------------------------+------------------+--------------------------+-------------------------+
+		+-----------------------------------------+------------------+--------------------------+-------------------------+
+		| Parameter Name                          | Config File Name | Value                    | ATS parent.config value |
+		+=========================================+==================+==========================+=========================+
+		| last.algorithm                          | parent.config    | true, false, strict,     | round_robin             |
+		|                                         |                  | consistent_hash          |                         |
+		+-----------------------------------------+------------------+--------------------------+-------------------------+
+		| last.parent_retry                       | parent.config    | simple_retry, both,      | parent_retry            |
+		|                                         |                  | unavailable_server_retry |                         |
+		+-----------------------------------------+------------------+--------------------------+-------------------------+
+		| last.unavailable_server_retry_responses | parent.config    | list of server response  | defaults to the value   |
+		|                                         |                  | codes, eg "500,502,503"  | in records.config       |
+		|                                         |                  |                          | when unused.            |
+		+-----------------------------------------+------------------+--------------------------+-------------------------+
+		| last.max_simple_retries                 | parent.config    | Nubmer of retries made   | defaults to the value   |
+		|                                         |                  | after a 4xx error        | in records.config       |
+		|                                         |                  |                          | when unused.            |
+		+-----------------------------------------+------------------+--------------------------+-------------------------+
+		| last.max_unavailable_server_retries     | parent.config    | Nubmer of retries made   | defaults to the value   |
+		|                                         |                  | after a 5xx error        | in records.config       |
+		|                                         |                  |                          | when unused.            |
+		+-----------------------------------------+------------------+--------------------------+-------------------------+
 
 
 		.. figure:: multi_site/ds_profile_parameters.png
 			:scale: 100%
 			:align: center
 
+    .. deprecated:: ATC 6.2
+
 	#) In the :term:`Delivery Service` page, select the newly created ``DS_PROFILE`` and save the :term:`Delivery Service`.
 
 #. Turn on parent_proxy_routing in the MID :term:`Profile`.
diff --git a/docs/source/overview/delivery_services.rst b/docs/source/overview/delivery_services.rst
index 811da8d1b4..d043383f35 100644
--- a/docs/source/overview/delivery_services.rst
+++ b/docs/source/overview/delivery_services.rst
@@ -1003,11 +1003,13 @@ The following :term:`Parameters` must have the :ref:`Config File <parameter-conf
 
 	.. impl-detail:: This :term:`Parameter` does not affect the contents of ``parent.config``, but instead ``strategies.yaml`` in :abbr:`ATS (Apache Traffic Server)` 9. It has the ``parent.config`` :ref:`parameter-config-file` value for consistency.
 
+- ``merge_parent_groups`` - on a Deliver Service :term:`Profile`, if this exists, moves each of the space-separated :term:`Cache Groups` named in the :ref:`parameter-value` from the secondary parent list into the primary parent list. This can be used to combine all parents into a single consistent hash ring.
+
 	.. deprecated:: ATCv6.2
 		In :ref:`to-api` version 4 (unstable at the time of this writing), TLS versions should be configured using the `TLS Versions`_ property of the Delivery Service, and support for this :term:`Parameter` will be removed at some point after the stabilization of :ref:`to-api` version 4.
 
-Parameters that Affect Multi-Site Origin
-''''''''''''''''''''''''''''''''''''''''
+Parameters that Affect Multi-Site Origin and inside a CDN
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Each :term:`Parameter` directly corresponds to a field in a line of the :abbr:`ATS (Apache Traffic Server)` `parent.config file <https://docs.trafficserver.apache.org/en/7.1.x/admin-guide/files/parent.config.en.html>`_ (usually by almost the same name), and documentation for these fields is provided in the form of links to their entries in the :abbr:`ATS (Apache Traffic Server)` documentation.
 
 .. _round_robin: https://docs.trafficserver.apache.org/en/9.1.x/admin-guide/files/parent.config.en.html#parent-config-format-round-robin
@@ -1023,33 +1025,33 @@ Each :term:`Parameter` directly corresponds to a field in a line of the :abbr:`A
 
 .. table:: :term:`Parameters` of a Delivery Service Profile_ that Affect :abbr:`MSO (Multi-Site-Origin)` Configuration
 
-	+---------------------------------------------+----------------------------------------------------------------------------+-------------------------------------------------------------------------------------+
-	| Name                                        | :abbr:`ATS (Apache Traffic Server)` `parent.config`_ field                 | Effect                                                                              |
-	+=============================================+============================================================================+=====================================================================================+
-	| mso.algorithm                               | `round_robin`_                                                             | Sets the algorithm used to determine from which :term:`origin server` content will  |
-	|                                             |                                                                            | be requested.                                                                       |
-	+---------------------------------------------+----------------------------------------------------------------------------+-------------------------------------------------------------------------------------+
-	| mso.max_simple_retries                      | `max_simple_retries`_                                                      | Sets a strict limit on the number of "simple retries" allowed before giving up      |
-	+---------------------------------------------+----------------------------------------------------------------------------+-------------------------------------------------------------------------------------+
-	| mso.max_unavailable_server_retries          | `max_unavailable_server_retries`_                                          | Sets a strict limit on the number of times the :term:`cache server` will attempt to |
-	|                                             |                                                                            | request content from an :term:`origin server` that has previously been considered   |
-	|                                             |                                                                            | "unavailable".                                                                      |
-	+---------------------------------------------+----------------------------------------------------------------------------+-------------------------------------------------------------------------------------+
-	| mso.parent_retry                            | `parent_retry`_                                                            | Sets whether the :term:`cache servers` will use "simple retries",                   |
-	|                                             |                                                                            | "unavailable server retries", or both.                                              |
-	+---------------------------------------------+----------------------------------------------------------------------------+-------------------------------------------------------------------------------------+
-	| mso.simple_retry_response_codes             | **UNKNOWN**                                                                | **UNKNOWN** - supposedly defines HTTP response codes from an :term:`origin server`  |
-	|                                             |                                                                            | that necessitate a "simple retry".                                                  |
-	+---------------------------------------------+----------------------------------------------------------------------------+-------------------------------------------------------------------------------------+
-	| mso.unavailable_server_retry_response_codes | `unavailable_server_retry_responses`_                                      | Defines HTTP response codes from an :term:`origin server` that indicate it is       |
-	|                                             |                                                                            | currently "unavailable".                                                            |
-	+---------------------------------------------+----------------------------------------------------------------------------+-------------------------------------------------------------------------------------+
-	| merge_parent_groups                         | `parent`_ and `secondary_parent`_                                          | Moves each of the space-separated :term:`Cache Groups` named in the                 |
-	|                                             |                                                                            | :ref:`parameter-value` from the secondary parent list into the primary parent list. |
-	|                                             |                                                                            | This can be used to combine all parents into a single consistent hash ring.         |
-	+---------------------------------------------+----------------------------------------------------------------------------+-------------------------------------------------------------------------------------+
-
-.. warning:: The ``mso.simple_retry_response_codes`` :term:`Parameter` has no apparent, possible use according to the :abbr:`ATS (Apache Traffic Server)` `parent.config documentation <https://docs.trafficserver.apache.org/en/9.1.x/admin-guide/files/parent.config.en.html>`_. Whether or not it has any effect - let alone the *intended* effect - is not known, and its use is therefore strongly discouraged.
+	+---------------------------------------------+--------------------------------------------------------+-------------------------------------------------------------------------------------+
+	| Name                                    | :abbr:`ATS (Apache Traffic Server)` `parent.config`_ field | Effect                                                                              |
+	+=========================================+============================================================+=====================================================================================+
+	| algorithm                               | `round_robin`_                                             | Sets the algorithm used to determine from which :term:`origin server` content will  |
+	|                                         |                                                            | be requested.                                                                       |
+	+-----------------------------------------+------------------------------------------------------------+-------------------------------------------------------------------------------------+
+	| max_simple_retries                      | `max_simple_retries`_                                      | Sets a strict limit on the number of "simple retries" allowed before giving up      |
+	+-----------------------------------------+------------------------------------------------------------+-------------------------------------------------------------------------------------+
+	| max_unavailable_server_retries          | `max_unavailable_server_retries`_                          | Sets a strict limit on the number of times the :term:`cache server` will attempt to |
+	|                                         |                                                            | request content from an :term:`origin server` that has previously been considered   |
+	|                                         |                                                            | "unavailable".                                                                      |
+	+-----------------------------------------+------------------------------------------------------------+-------------------------------------------------------------------------------------+
+	| parent_retry                            | `parent_retry`_                                            | Sets whether the :term:`cache servers` will use "simple retries",                   |
+	|                                         |                                                            | "unavailable server retries", or both.                                              |
+	+-----------------------------------------+------------------------------------------------------------+-------------------------------------------------------------------------------------+
+	| simple_retry_response_codes             | **UNKNOWN**                                                | **UNKNOWN** - supposedly defines HTTP response codes from an :term:`origin server`  |
+	|                                         |                                                            | that necessitate a "simple retry".                                                  |
+	+-----------------------------------------+------------------------------------------------------------+-------------------------------------------------------------------------------------+
+	| unavailable_server_retry_response_codes | `unavailable_server_retry_responses`_                      | Defines HTTP response codes from an :term:`origin server` that indicate it is       |
+	|                                         |                                                            | currently "unavailable".                                                            |
+	+-----------------------------------------+------------------------------------------------------------+-------------------------------------------------------------------------------------+
+
+The above :term:`Parameters` are supported for ``first``, ``inner`` and ``last`` tiers by specifying prefixes ``first.``, ``inner.`` and ``last.``, applicable to both topology and non topology.  This allows fine tuning of markdown and retry behavior inside a CDN.
+
+.. deprecated:: The ``mso.`` prefix is deprecated.  ``last.`` prefix should be preferred although no prefix can also be used.
+
+.. warning:: The `simple_retry_response_codes`` :term:`Parameter` has no apparent, possible use according to the :abbr:`ATS (Apache Traffic Server)` `parent.config documentation <https://docs.trafficserver.apache.org/en/9.1.x/admin-guide/files/parent.config.en.html>`_. Whether or not it has any effect - let alone the *intended* effect - is not known, and its use is therefore strongly discouraged.
 
 .. seealso:: To see how the :ref:`Values <parameter-value>` of these Parameters are interpreted, refer to the `Apache Traffic Server documentation on the parent.config configuration file <https://docs.trafficserver.apache.org/en/7.1.x/admin-guide/files/parent.config.en.html>`_
 
diff --git a/lib/go-atscfg/parentdotconfig.go b/lib/go-atscfg/parentdotconfig.go
index b57a920720..c23c72382c 100644
--- a/lib/go-atscfg/parentdotconfig.go
+++ b/lib/go-atscfg/parentdotconfig.go
@@ -39,20 +39,7 @@ const LineCommentParentDotConfig = LineCommentHash
 const ParentConfigFileName = "parent.config"
 
 const ParentConfigParamQStringHandling = "psel.qstring_handling"
-const ParentConfigParamMSOAlgorithm = "mso.algorithm"
-const ParentConfigParamMSOParentRetry = "mso.parent_retry"
-const ParentConfigParamMSOUnavailableServerRetryResponses = "mso.unavailable_server_retry_responses"
-const ParentConfigParamMSOMaxSimpleRetries = "mso.max_simple_retries"
-const ParentConfigParamMSOMaxUnavailableServerRetries = "mso.max_unavailable_server_retries"
 const ParentConfigParamMergeGroups = "merge_parent_groups"
-const ParentConfigParamAlgorithm = "algorithm"
-const ParentConfigParamQString = "qstring"
-const ParentConfigParamSecondaryMode = "try_all_primaries_before_secondary"
-
-const ParentConfigParamParentRetry = "parent_retry"
-const ParentConfigParamUnavailableServerRetryResponses = "unavailable_server_retry_responses"
-const ParentConfigParamMaxSimpleRetries = "max_simple_retries"
-const ParentConfigParamMaxUnavailableServerRetries = "max_unavailable_server_retries"
 
 const ParentConfigDSParamDefaultMSOAlgorithm = ParentAbstractionServiceRetryPolicyConsistentHash
 const ParentConfigDSParamDefaultMSOParentRetry = "both"
@@ -67,6 +54,38 @@ const ParentConfigCacheParamRank = "rank"
 const ParentConfigCacheParamNotAParent = "not_a_parent"
 const StrategyConfigUsePeering = "use_peering"
 
+// same across DS
+const ParentConfigParamQString = "qstring"
+
+type ParentConfigRetryKeys struct {
+	Algorithm                 string
+	SecondaryMode             string
+	ParentRetry               string
+	MaxSimpleRetries          string
+	SimpleRetryResponses      string
+	MaxUnavailableRetries     string
+	UnavailableRetryResponses string
+}
+
+func MakeParentConfigRetryKeysWithPrefix(prefix string) ParentConfigRetryKeys {
+	return ParentConfigRetryKeys{
+		Algorithm:                 prefix + "algorithm",
+		SecondaryMode:             prefix + "try_all_primaries_before_secondary",
+		ParentRetry:               prefix + "parent_retry",
+		MaxSimpleRetries:          prefix + "max_simple_retries",
+		SimpleRetryResponses:      prefix + "simple_server_retry_responses",
+		MaxUnavailableRetries:     prefix + "max_unavailable_server_retries",
+		UnavailableRetryResponses: prefix + "unavailable_server_retry_responses",
+	}
+}
+
+var ParentConfigRetryKeysFirst = MakeParentConfigRetryKeysWithPrefix("first.")
+var ParentConfigRetryKeysInner = MakeParentConfigRetryKeysWithPrefix("inner.")
+var ParentConfigRetryKeysLast = MakeParentConfigRetryKeysWithPrefix("last.")
+
+var ParentConfigRetryKeysMSO = MakeParentConfigRetryKeysWithPrefix("mso.")
+var ParentConfigRetryKeysDefault = MakeParentConfigRetryKeysWithPrefix("")
+
 type OriginHost string
 type OriginFQDN string
 
@@ -331,6 +350,7 @@ func makeParentDotConfigData(
 	warnings = append(warnings, dsOriginWarns...)
 
 	for _, ds := range dses {
+
 		if ds.XMLID == nil || *ds.XMLID == "" {
 			warnings = append(warnings, "got ds with missing XMLID, skipping!")
 			continue
@@ -357,14 +377,11 @@ func makeParentDotConfigData(
 			continue
 		}
 
-		// Note these Parameters are only used for MSO for legacy DeliveryServiceServers DeliveryServices (except QueryStringHandling which is used by all DeliveryServices).
-		//      Topology DSes use them for all DSes, MSO and non-MSO.
-		dsParams, dsParamsWarnings := getParentDSParams(ds, profileParentConfigParams)
-		warnings = append(warnings, dsParamsWarnings...)
+		isMSO := ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin
 
 		// TODO put these in separate functions. No if-statement should be this long.
 		if ds.Topology != nil && *ds.Topology != "" {
-			txt, topoWarnings, err := getTopologyParentConfigLine(
+			pasvc, topoWarnings, err := getTopologyParentConfigLine(
 				server,
 				serversWithParams,
 				&ds,
@@ -374,7 +391,8 @@ func makeParentDotConfigData(
 				serverCapabilities,
 				dsRequiredCapabilities,
 				cacheGroups,
-				dsParams,
+				profileParentConfigParams,
+				isMSO,
 				atsMajorVer,
 				dsOrigins[DeliveryServiceID(*ds.ID)],
 				opt.AddComments,
@@ -386,214 +404,231 @@ func makeParentDotConfigData(
 				continue
 			}
 
-			if txt != nil { // will be nil with no error if this server isn't in the Topology, or if it doesn't have the Required Capabilities
-				parentAbstraction.Services = append(parentAbstraction.Services, txt)
-			}
-		} else if cacheIsTopLevel {
-			parentQStr := false
-			if dsParams.QueryStringHandling == "" && dsParams.Algorithm == tc.AlgorithmConsistentHash && ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp {
-				parentQStr = true
+			if pasvc != nil { // will be nil with no error if this server isn't in the Topology, or if it doesn't have the Required Capabilities
+				parentAbstraction.Services = append(parentAbstraction.Services, pasvc)
 			}
-
-			orgFQDNStr := *ds.OrgServerFQDN
-			// if this cache isn't the last tier, i.e. we're not going to the origin, use http not https
-			if isLastCacheTier := noTopologyServerIsLastCacheForDS(server, &ds); !isLastCacheTier {
-				orgFQDNStr = strings.Replace(orgFQDNStr, `https://`, `http://`, -1)
-			}
-			orgURI, orgWarns, err := getOriginURI(orgFQDNStr)
-			warnings = append(warnings, orgWarns...)
-			if err != nil {
-				warnings = append(warnings, "DS '"+*ds.XMLID+"' has malformed origin URI: '"+orgFQDNStr+"': skipping!"+err.Error())
-				continue
+		} else {
+			isLastCacheTier := noTopologyServerIsLastCacheForDS(server, &ds)
+			serverPlacement := TopologyPlacement{
+				IsLastCacheTier:  isLastCacheTier,
+				IsFirstCacheTier: !isLastCacheTier || !ds.Type.UsesMidCache(),
 			}
 
-			textLine := &ParentAbstractionService{}
-			textLine.Name = *ds.XMLID
+			dsParams, dswarns := getParentDSParams(ds, profileParentConfigParams, serverPlacement, isMSO)
+			warnings = append(warnings, dswarns...)
 
-			if ds.OriginShield != nil && *ds.OriginShield != "" {
+			if cacheIsTopLevel {
+				parentQStr := false
+				if dsParams.QueryStringHandling == "" && dsParams.Algorithm == tc.AlgorithmConsistentHash && ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp {
+					parentQStr = true
+				}
 
-				policy := ParentAbstractionServiceRetryPolicyConsistentHash
-				if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; strings.TrimSpace(parentSelectAlg) != "" {
-					paramPolicy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(parentSelectAlg)
-					if paramPolicy != ParentAbstractionServiceRetryPolicyInvalid {
-						policy = paramPolicy
-					} else {
-						warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamAlgorithm+" parameter '"+parentSelectAlg+"', not using!")
-					}
+				orgFQDNStr := *ds.OrgServerFQDN
+				// if this cache isn't the last tier, i.e. we're not going to the origin, use http not https
+				if !isLastCacheTier {
+					orgFQDNStr = strings.Replace(orgFQDNStr, `https://`, `http://`, -1)
 				}
-				textLine.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
-				textLine.DestDomain = orgURI.Hostname()
-				textLine.Port, err = strconv.Atoi(orgURI.Port())
+				orgURI, orgWarns, err := getOriginURI(orgFQDNStr)
+				warnings = append(warnings, orgWarns...)
 				if err != nil {
-					if strings.ToLower(orgURI.Scheme) == "https" {
-						textLine.Port = 443
-					} else {
-						textLine.Port = 80
-					}
-					warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+orgURI.Port()+"': using "+strconv.Itoa(textLine.Port)+"! : "+err.Error())
+					warnings = append(warnings, "DS '"+*ds.XMLID+"' has malformed origin URI: '"+orgFQDNStr+"': skipping!"+err.Error())
+					continue
 				}
 
-				fqdnPort := strings.Split(*ds.OriginShield, ":")
-				parent := &ParentAbstractionServiceParent{}
-				parent.FQDN = fqdnPort[0]
-				if len(fqdnPort) > 1 {
-					parent.Port, err = strconv.Atoi(fqdnPort[1])
+				pasvc := &ParentAbstractionService{}
+				pasvc.Name = *ds.XMLID
+
+				if ds.OriginShield != nil && *ds.OriginShield != "" {
+
+					policy := ParentAbstractionServiceRetryPolicyConsistentHash
+
+					if parentSelectAlg := serverParams[ParentConfigRetryKeysDefault.Algorithm]; strings.TrimSpace(parentSelectAlg) != "" {
+						paramPolicy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(parentSelectAlg)
+						if paramPolicy != ParentAbstractionServiceRetryPolicyInvalid {
+							policy = paramPolicy
+						} else {
+							warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigRetryKeysDefault.Algorithm+" parameter '"+parentSelectAlg+"', not using!")
+						}
+					}
+					pasvc.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
+					pasvc.DestDomain = orgURI.Hostname()
+					pasvc.Port, err = strconv.Atoi(orgURI.Port())
 					if err != nil {
+						if strings.ToLower(orgURI.Scheme) == "https" {
+							pasvc.Port = 443
+						} else {
+							pasvc.Port = 80
+						}
+						warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+orgURI.Port()+"': using "+strconv.Itoa(pasvc.Port)+"! : "+err.Error())
+					}
+
+					fqdnPort := strings.Split(*ds.OriginShield, ":")
+					parent := &ParentAbstractionServiceParent{}
+					parent.FQDN = fqdnPort[0]
+					if len(fqdnPort) > 1 {
+						parent.Port, err = strconv.Atoi(fqdnPort[1])
+						if err != nil {
+							parent.Port = 80
+							warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+*ds.OriginShield+"': using "+strconv.Itoa(parent.Port)+"! : "+err.Error())
+						}
+					} else {
 						parent.Port = 80
-						warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+*ds.OriginShield+"': using "+strconv.Itoa(parent.Port)+"! : "+err.Error())
+						warnings = append(warnings, "DS '"+*ds.XMLID+"' had no origin port: '"+*ds.OriginShield+"': using "+strconv.Itoa(parent.Port)+"!")
 					}
-				} else {
-					parent.Port = 80
-					warnings = append(warnings, "DS '"+*ds.XMLID+"' had no origin port: '"+*ds.OriginShield+"': using "+strconv.Itoa(parent.Port)+"!")
-				}
-				textLine.Parents = append(textLine.Parents, parent)
-				textLine.RetryPolicy = policy
-				textLine.GoDirect = true
+					pasvc.Parents = append(pasvc.Parents, parent)
+					pasvc.RetryPolicy = policy
+					pasvc.GoDirect = true
 
-				// textLine += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port() + " parent=" + *ds.OriginShield + " " + algorithm + " go_direct=true\n"
+					// textLine += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port() + " parent=" + *ds.OriginShield + " " + algorithm + " go_direct=true\n"
 
-			} else if ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin {
-				textLine.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
-				textLine.DestDomain = orgURI.Hostname()
-				textLine.Port, err = strconv.Atoi(orgURI.Port())
-				if err != nil {
-					textLine.Port = 80
-					warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+orgURI.Port()+"': using "+strconv.Itoa(textLine.Port)+"! : "+err.Error())
-				}
+				} else if ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin {
+					pasvc.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
+					pasvc.DestDomain = orgURI.Hostname()
+					pasvc.Port, err = strconv.Atoi(orgURI.Port())
+					if err != nil {
+						pasvc.Port = 80
+						warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+orgURI.Port()+"': using "+strconv.Itoa(pasvc.Port)+"! : "+err.Error())
+					}
 
-				// textLine += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port() + " "
-				if len(parentInfos) == 0 {
-				}
+					// textLine += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port() + " "
+					if len(parentInfos) == 0 {
+					}
 
-				if len(parentInfos[OriginHost(orgURI.Hostname())]) == 0 {
-					// TODO error? emulates Perl
-					warnings = append(warnings, "DS "+*ds.XMLID+" has no parent servers")
-				}
+					if len(parentInfos[OriginHost(orgURI.Hostname())]) == 0 {
+						// TODO error? emulates Perl
+						warnings = append(warnings, "DS "+*ds.XMLID+" has no parent servers")
+					}
 
-				parents, secondaryParents, secondaryMode, parentWarns := getMSOParentStrs(&ds, parentInfos[OriginHost(orgURI.Hostname())], atsMajorVer, dsParams.Algorithm, dsParams.TryAllPrimariesBeforeSecondary)
-				warnings = append(warnings, parentWarns...)
-				textLine.Parents = parents
-				textLine.SecondaryParents = secondaryParents
-				textLine.SecondaryMode = secondaryMode
-				textLine.RetryPolicy = dsParams.Algorithm // TODO convert
-				textLine.IgnoreQueryStringInParentSelection = !parentQStr
-				textLine.GoDirect = true
-
-				// textLine += parents + secondaryParents + ` round_robin=` + dsParams.Algorithm + ` qstring=` + parentQStr + ` go_direct=false parent_is_proxy=false`
-				prWarns := []string{}
-				textLine.MaxSimpleRetries, textLine.MaxMarkdownRetries, textLine.MarkdownResponseCodes, textLine.ErrorResponseCodes, prWarns = getParentRetryStr(true, atsMajorVer, dsParams.ParentRetry, dsParams.UnavailableServerRetryResponses, dsParams.MaxSimpleRetries, dsParams.MaxUnavailableServerRetries)
-				warnings = append(warnings, prWarns...)
+					parents, secondaryParents, secondaryMode, parentWarns := getMSOParentStrs(&ds, parentInfos[OriginHost(orgURI.Hostname())], atsMajorVer, dsParams.Algorithm, dsParams.TryAllPrimariesBeforeSecondary)
+					warnings = append(warnings, parentWarns...)
+					pasvc.Parents = parents
+					pasvc.SecondaryParents = secondaryParents
+					pasvc.SecondaryMode = secondaryMode
+					pasvc.RetryPolicy = dsParams.Algorithm // TODO convert
+					pasvc.IgnoreQueryStringInParentSelection = !parentQStr
+					pasvc.GoDirect = true
 
-				parentAbstraction.Services = append(parentAbstraction.Services, textLine)
-			}
-		} else {
-			queryStringHandling := ParentSelectParamQStringHandlingToBool(serverParams[ParentConfigParamQStringHandling]) // "qsh" in Perl
-			if queryStringHandling == nil && serverParams[ParentConfigParamQStringHandling] != "" {
-				warnings = append(warnings, "Server Parameter '"+ParentConfigParamQStringHandling+"' value '"+serverParams[ParentConfigParamQStringHandling]+"' malformed, not using!")
-			}
+					// textLine += parents + secondaryParents + ` round_robin=` + dsParams.Algorithm + ` qstring=` + parentQStr + ` go_direct=false parent_is_proxy=false`
 
-			roundRobin := ParentAbstractionServiceRetryPolicyConsistentHash
-			// roundRobin := `round_robin=consistent_hash`
-			goDirect := false
-			// goDirect := `go_direct=false`
+					prWarns := dsParams.FillParentSvcRetries(cacheIsTopLevel, atsMajorVer, pasvc)
+					warnings = append(warnings, prWarns...)
 
-			parents, secondaryParents, secondaryMode, parentWarns := getParentStrs(&ds, dsRequiredCapabilities, parentInfos[deliveryServicesAllParentsKey], atsMajorVer, dsParams.TryAllPrimariesBeforeSecondary)
-			warnings = append(warnings, parentWarns...)
+					parentAbstraction.Services = append(parentAbstraction.Services, pasvc)
+				}
+			} else {
+				queryStringHandling := ParentSelectParamQStringHandlingToBool(serverParams[ParentConfigParamQStringHandling]) // "qsh" in Perl
+				if queryStringHandling == nil && serverParams[ParentConfigParamQStringHandling] != "" {
+					warnings = append(warnings, "Server Parameter '"+ParentConfigParamQStringHandling+"' value '"+serverParams[ParentConfigParamQStringHandling]+"' malformed, not using!")
+				}
 
-			text := &ParentAbstractionService{}
-			text.Name = *ds.XMLID
+				roundRobin := ParentAbstractionServiceRetryPolicyConsistentHash
+				// roundRobin := `round_robin=consistent_hash`
+				goDirect := false
+				// goDirect := `go_direct=false`
 
-			// peering ring check
-			if dsParams.UsePeering {
-				secondaryMode = ParentAbstractionServiceParentSecondaryModePeering
-			}
+				parents, secondaryParents, secondaryMode, parentWarns := getParentStrs(&ds, dsRequiredCapabilities, parentInfos[deliveryServicesAllParentsKey], atsMajorVer, dsParams.TryAllPrimariesBeforeSecondary)
+				warnings = append(warnings, parentWarns...)
 
-			orgFQDNStr := *ds.OrgServerFQDN
-			// if this cache isn't the last tier, i.e. we're not going to the origin, use http not https
-			if isLastCacheTier := noTopologyServerIsLastCacheForDS(server, &ds); !isLastCacheTier {
-				orgFQDNStr = strings.Replace(orgFQDNStr, `https://`, `http://`, -1)
-			}
-			orgURI, orgWarns, err := getOriginURI(orgFQDNStr)
-			warnings = append(warnings, orgWarns...)
-			if err != nil {
-				warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  URI: '"+*ds.OrgServerFQDN+"': skipping!"+err.Error())
-				continue
-			}
+				pasvc := &ParentAbstractionService{}
+				pasvc.Name = *ds.XMLID
 
-			text.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
+				// peering ring check
+				if dsParams.UsePeering {
+					secondaryMode = ParentAbstractionServiceParentSecondaryModePeering
+				}
 
-			// TODO encode this in a DSType func, IsGoDirect() ?
-			if *ds.Type == tc.DSTypeHTTPNoCache || *ds.Type == tc.DSTypeHTTPLive || *ds.Type == tc.DSTypeDNSLive {
-				text.DestDomain = orgURI.Hostname()
-				text.Port, err = strconv.Atoi(orgURI.Port())
+				orgFQDNStr := *ds.OrgServerFQDN
+				// if this cache isn't the last tier, i.e. we're not going to the origin, use http not https
+				if !isLastCacheTier {
+					orgFQDNStr = strings.Replace(orgFQDNStr, `https://`, `http://`, -1)
+				}
+				orgURI, orgWarns, err := getOriginURI(orgFQDNStr)
+				warnings = append(warnings, orgWarns...)
 				if err != nil {
-					if strings.ToLower(orgURI.Scheme) == "https" {
-						text.Port = 443
-					} else {
-						text.Port = 80
-					}
-					warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+orgURI.Port()+"': using "+strconv.Itoa(text.Port)+"! : "+err.Error())
+					warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  URI: '"+*ds.OrgServerFQDN+"': skipping!"+err.Error())
+					continue
 				}
-				text.GoDirect = true
-
-				text.Parents = []*ParentAbstractionServiceParent{&ParentAbstractionServiceParent{
-					FQDN:   text.DestDomain,
-					Port:   text.Port,
-					Weight: 1.0,
-				}}
 
-				// text += `dest_domain=` + orgURI.Hostname() + ` port=` + orgURI.Port() + ` go_direct=true` + "\n"
+				pasvc.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
 
-			} else {
-				// check for profile psel.qstring_handling.  If this parameter is assigned to the server profile,
-				// then edges will use the qstring handling value specified in the parameter for all profiles.
-
-				// If there is no defined parameter in the profile, then check the delivery service profile.
-				// If psel.qstring_handling exists in the DS profile, then we use that value for the specified DS only.
-				// This is used only if not overridden by a server profile qstring handling parameter.
-
-				// TODO refactor this logic, hard to understand (transliterated from Perl)
-				dsQSH := queryStringHandling
-				if dsQSH == nil {
-					dsQSH = ParentSelectParamQStringHandlingToBool(dsParams.QueryStringHandling)
-					if dsQSH == nil && dsParams.QueryStringHandling != "" {
-						warnings = append(warnings, "Delivery Service parameter '"+ParentConfigParamQStringHandling+"' value '"+dsParams.QueryStringHandling+"' malformed, not using!")
+				// TODO encode this in a DSType func, IsGoDirect() ?
+				if *ds.Type == tc.DSTypeHTTPNoCache || *ds.Type == tc.DSTypeHTTPLive || *ds.Type == tc.DSTypeDNSLive {
+					pasvc.DestDomain = orgURI.Hostname()
+					pasvc.Port, err = strconv.Atoi(orgURI.Port())
+					if err != nil {
+						if strings.ToLower(orgURI.Scheme) == "https" {
+							pasvc.Port = 443
+						} else {
+							pasvc.Port = 80
+						}
+						warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+orgURI.Port()+"': using "+strconv.Itoa(pasvc.Port)+"! : "+err.Error())
 					}
 
-				}
-				parentQStr := dsQSH
-				if parentQStr == nil {
-					v := false
-					parentQStr = &v
-				}
-				if ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp && dsQSH == nil {
-					v := true
-					parentQStr = &v
-				}
-				if parentQStr == nil {
-					b := !DefaultIgnoreQueryStringInParentSelection
-					parentQStr = &b
-				}
+					pasvc.GoDirect = true
 
-				text.DestDomain = orgURI.Hostname()
-				text.Port, err = strconv.Atoi(orgURI.Port())
-				if err != nil {
-					if strings.ToLower(orgURI.Scheme) == "https" {
-						text.Port = 443
-					} else {
-						text.Port = 80
+					pasvc.Parents = []*ParentAbstractionServiceParent{&ParentAbstractionServiceParent{
+						FQDN:   pasvc.DestDomain,
+						Port:   pasvc.Port,
+						Weight: 1.0,
+					}}
+
+					// text += `dest_domain=` + orgURI.Hostname() + ` port=` + orgURI.Port() + ` go_direct=true` + "\n"
+				} else {
+
+					// check for profile psel.qstring_handling.  If this parameter is assigned to the server profile,
+					// then edges will use the qstring handling value specified in the parameter for all profiles.
+
+					// If there is no defined parameter in the profile, then check the delivery service profile.
+					// If psel.qstring_handling exists in the DS profile, then we use that value for the specified DS only.
+					// This is used only if not overridden by a server profile qstring handling parameter.
+
+					// TODO refactor this logic, hard to understand (transliterated from Perl)
+					dsQSH := queryStringHandling
+					if dsQSH == nil {
+						dsQSH = ParentSelectParamQStringHandlingToBool(dsParams.QueryStringHandling)
+						if dsQSH == nil && dsParams.QueryStringHandling != "" {
+							warnings = append(warnings, "Delivery Service parameter '"+ParentConfigParamQStringHandling+"' value '"+dsParams.QueryStringHandling+"' malformed, not using!")
+						}
+
+					}
+					parentQStr := dsQSH
+					if parentQStr == nil {
+						v := false
+						parentQStr = &v
+					}
+					if ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp && dsQSH == nil {
+						v := true
+						parentQStr = &v
+					}
+					if parentQStr == nil {
+						b := !DefaultIgnoreQueryStringInParentSelection
+						parentQStr = &b
 					}
-					warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+orgURI.Port()+"': using "+strconv.Itoa(text.Port)+"! : "+err.Error())
+
+					pasvc.DestDomain = orgURI.Hostname()
+					pasvc.Port, err = strconv.Atoi(orgURI.Port())
+					if err != nil {
+						if strings.ToLower(orgURI.Scheme) == "https" {
+							pasvc.Port = 443
+						} else {
+							pasvc.Port = 80
+						}
+						warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin  port: '"+orgURI.Port()+"': using "+strconv.Itoa(pasvc.Port)+"! : "+err.Error())
+					}
+					pasvc.Parents = parents
+					pasvc.SecondaryParents = secondaryParents
+					pasvc.SecondaryMode = secondaryMode
+					pasvc.RetryPolicy = roundRobin
+					pasvc.GoDirect = goDirect
+					pasvc.IgnoreQueryStringInParentSelection = !*parentQStr
+					// text += `dest_domain=` + orgURI.Hostname() + ` port=` + orgURI.Port() + ` ` + parents + ` ` + secondaryParents + ` ` + roundRobin + ` ` + goDirect + ` qstring=` + parentQStr + "\n"
 				}
-				text.Parents = parents
-				text.SecondaryParents = secondaryParents
-				text.SecondaryMode = secondaryMode
-				text.RetryPolicy = roundRobin
-				text.GoDirect = goDirect
-				text.IgnoreQueryStringInParentSelection = !*parentQStr
-				// text += `dest_domain=` + orgURI.Hostname() + ` port=` + orgURI.Port() + ` ` + parents + ` ` + secondaryParents + ` ` + roundRobin + ` ` + goDirect + ` qstring=` + parentQStr + "\n"
+
+				prWarns := dsParams.FillParentSvcRetries(cacheIsTopLevel, atsMajorVer, pasvc)
+				warnings = append(warnings, prWarns...)
+
+				parentAbstraction.Services = append(parentAbstraction.Services, pasvc)
 			}
-			parentAbstraction.Services = append(parentAbstraction.Services, text)
 		}
 	}
 
@@ -612,7 +647,8 @@ func makeParentDotConfigData(
 		defaultDestText.DestDomain = `.`
 		defaultDestText.Parents = parents
 		// defaultDestText = `dest_domain=. ` + parents
-		if serverParams[ParentConfigParamAlgorithm] == tc.AlgorithmConsistentHash {
+
+		if serverParams[ParentConfigRetryKeysDefault.Algorithm] == tc.AlgorithmConsistentHash {
 			defaultDestText.SecondaryParents = secondaryParents
 			defaultDestText.SecondaryMode = secondaryMode
 			// defaultDestText += secondaryParents
@@ -802,32 +838,101 @@ type originURI struct {
 const deliveryServicesAllParentsKey = "all_parents"
 
 type parentDSParams struct {
-	Algorithm                       ParentAbstractionServiceRetryPolicy
+	Algorithm           ParentAbstractionServiceRetryPolicy
+	QueryStringHandling string
+
+	HasRetryParams                  bool
 	ParentRetry                     string
-	UnavailableServerRetryResponses string
 	MaxSimpleRetries                string
 	MaxUnavailableServerRetries     string
-	QueryStringHandling             string
+	SimpleServerRetryResponses      string
+	UnavailableServerRetryResponses string
 	TryAllPrimariesBeforeSecondary  bool
-	UsePeering                      bool
-	MergeGroups                     []string
+
+	UsePeering  bool
+	MergeGroups []string
+}
+
+// FillParentRetries populates the parentDSParams retries values from the ds parameters map for given ds parameter keys.
+// Returns if any params found and any warnings.
+func (dsp *parentDSParams) FillParentRetries(keys ParentConfigRetryKeys, dsParams map[string]string, dsid string) (bool, []string) {
+	var warnings []string
+	hasValues := false
+
+	if v, ok := dsParams[keys.Algorithm]; ok && strings.TrimSpace(v) != "" {
+		policy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(v)
+		if policy != ParentAbstractionServiceRetryPolicyInvalid {
+			dsp.Algorithm = policy
+			hasValues = true
+		} else {
+			warnings = append(warnings, "DS '"+dsid+"' had malformed "+keys.Algorithm+" parameter '"+v+"', not using!")
+		}
+	}
+
+	if v, ok := dsParams[keys.ParentRetry]; ok {
+		dsp.ParentRetry = v
+		hasValues = true
+	}
+
+	if v, ok := dsParams[keys.MaxSimpleRetries]; ok {
+		dsp.MaxSimpleRetries = v
+		hasValues = true
+	}
+	if v, ok := dsParams[keys.MaxUnavailableRetries]; ok {
+		dsp.MaxUnavailableServerRetries = v
+		hasValues = true
+	}
+
+	if v, ok := dsParams[keys.SimpleRetryResponses]; ok {
+		if unavailableServerRetryResponsesValid(v) {
+			dsp.SimpleServerRetryResponses = v
+			hasValues = true
+		} else {
+			warnings = append(warnings, "DS '"+dsid+"' had malformed "+keys.SimpleRetryResponses+" parameter '"+v+"', not using!")
+		}
+	}
+
+	if v, ok := dsParams[keys.UnavailableRetryResponses]; ok {
+		if unavailableServerRetryResponsesValid(v) {
+			dsp.UnavailableServerRetryResponses = v
+			hasValues = true
+		} else {
+			warnings = append(warnings, "DS '"+dsid+"' had malformed "+keys.UnavailableRetryResponses+" parameter '"+v+"', not using!")
+		}
+	}
+
+	if v, ok := dsParams[keys.SecondaryMode]; ok {
+		if v == "false" {
+			dsp.TryAllPrimariesBeforeSecondary = false
+		} else {
+			dsp.TryAllPrimariesBeforeSecondary = true
+			if v != "" {
+				warnings = append(warnings, "DS '"+dsid+"' had Parameter "+keys.SecondaryMode+" which is used if it exists, the value is ignored (unless false) ! Non-empty value '"+v+"' will be ignored!")
+			}
+		}
+	}
+
+	return hasValues, warnings
 }
 
 // getDSParams returns the Delivery Service Profile Parameters used in parent.config, and any warnings.
 // If Parameters don't exist, defaults are returned. Non-MSO Delivery Services default to no custom retry logic (we should reevaluate that).
 // Note these Parameters are only used for MSO for legacy DeliveryServiceServers DeliveryServices.
 //      Topology DSes use them for all DSes, MSO and non-MSO.
-func getParentDSParams(ds DeliveryService, profileParentConfigParams map[string]map[string]string) (parentDSParams, []string) {
+func getParentDSParams(ds DeliveryService, profileParentConfigParams map[string]map[string]string, serverPlacement TopologyPlacement, isMSO bool) (parentDSParams, []string) {
 	warnings := []string{}
 	params := parentDSParams{}
-	isMSO := ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin
-	if isMSO {
+
+	// Default values for origin facing MSO tier
+	if serverPlacement.IsLastCacheTier && isMSO {
 		params.Algorithm = ParentConfigDSParamDefaultMSOAlgorithm
 		params.ParentRetry = ParentConfigDSParamDefaultMSOParentRetry
 		params.UnavailableServerRetryResponses = ParentConfigDSParamDefaultMSOUnavailableServerRetryResponses
 		params.MaxSimpleRetries = strconv.Itoa(ParentConfigDSParamDefaultMaxSimpleRetries)
 		params.MaxUnavailableServerRetries = strconv.Itoa(ParentConfigDSParamDefaultMaxUnavailableServerRetries)
+		params.HasRetryParams = true
 	}
+
 	if ds.ProfileName == nil || *ds.ProfileName == "" {
 		return params, warnings
 	}
@@ -840,68 +945,42 @@ func getParentDSParams(ds DeliveryService, profileParentConfigParams map[string]
 			params.UsePeering = true
 		}
 	}
+
 	// the following may be blank, no default
 	params.QueryStringHandling = dsParams[ParentConfigParamQStringHandling]
 	params.MergeGroups = strings.Split(dsParams[ParentConfigParamMergeGroups], " ")
 
-	// TODO deprecate & remove "mso." Parameters - there was never a reason to restrict these settings to MSO.
-	if isMSO {
-		if v, ok := dsParams[ParentConfigParamMSOAlgorithm]; ok && strings.TrimSpace(v) != "" {
-			policy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(v)
-			if policy != ParentAbstractionServiceRetryPolicyInvalid {
-				params.Algorithm = policy
-			} else {
-				warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamMSOAlgorithm+" parameter '"+v+"', not using!")
-			}
-		}
-		if v, ok := dsParams[ParentConfigParamMSOParentRetry]; ok {
-			params.ParentRetry = v
-		}
-		if v, ok := dsParams[ParentConfigParamMSOUnavailableServerRetryResponses]; ok {
-			if v != "" && !unavailableServerRetryResponsesValid(v) {
-				warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamMSOUnavailableServerRetryResponses+" parameter '"+v+"', not using!")
-			} else if v != "" {
-				params.UnavailableServerRetryResponses = v
-			}
+	// progressively fill in the params
+	if serverPlacement.IsLastCacheTier {
+		// mso. prefix lowest priority
+		if isMSO {
+			hasVals, warns := params.FillParentRetries(ParentConfigRetryKeysMSO, dsParams, *ds.XMLID)
+			params.HasRetryParams = params.HasRetryParams || hasVals
+			warnings = append(warnings, warns...)
 		}
-		if v, ok := dsParams[ParentConfigParamMSOMaxSimpleRetries]; ok {
-			params.MaxSimpleRetries = v
-		}
-		if v, ok := dsParams[ParentConfigParamMSOMaxUnavailableServerRetries]; ok {
-			params.MaxUnavailableServerRetries = v
-		}
-	}
 
-	// Even if the DS is MSO, non-"mso." Parameters override "mso." ones, because they're newer.
-	if v, ok := dsParams[ParentConfigParamAlgorithm]; ok && strings.TrimSpace(v) != "" {
-		policy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(v)
-		if policy != ParentAbstractionServiceRetryPolicyInvalid {
-			params.Algorithm = policy
-		} else {
-			warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamAlgorithm+" parameter '"+v+"', not using!")
-		}
-	}
-	if v, ok := dsParams[ParentConfigParamParentRetry]; ok {
-		params.ParentRetry = v
-	}
-	if v, ok := dsParams[ParentConfigParamUnavailableServerRetryResponses]; ok {
-		if v != "" && !unavailableServerRetryResponsesValid(v) {
-			warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamUnavailableServerRetryResponses+" parameter '"+v+"', not using!")
-		} else if v != "" {
-			params.UnavailableServerRetryResponses = v
-		}
-	}
-	if v, ok := dsParams[ParentConfigParamMaxSimpleRetries]; ok {
-		params.MaxSimpleRetries = v
-	}
-	if v, ok := dsParams[ParentConfigParamMaxUnavailableServerRetries]; ok {
-		params.MaxUnavailableServerRetries = v
-	}
-	if v, ok := dsParams[ParentConfigParamSecondaryMode]; ok {
-		if v != "" {
-			warnings = append(warnings, "DS '"+*ds.XMLID+"' had Parameter "+ParentConfigParamSecondaryMode+" which is used if it exists, the value is ignored! Non-empty value '"+v+"' will be ignored!")
-		}
-		params.TryAllPrimariesBeforeSecondary = true
+		// no prefix next priority
+		hasVals, warns := params.FillParentRetries(ParentConfigRetryKeysDefault, dsParams, *ds.XMLID)
+		warnings = append(warnings, warns...)
+		params.HasRetryParams = params.HasRetryParams || hasVals
+
+		// last. prefix highest priority
+		hasVals, warns = params.FillParentRetries(ParentConfigRetryKeysLast, dsParams, *ds.XMLID)
+		warnings = append(warnings, warns...)
+		params.HasRetryParams = params.HasRetryParams || hasVals
+
+	} else if serverPlacement.IsInnerCacheTier {
+		hasVals, warns := params.FillParentRetries(ParentConfigRetryKeysInner, dsParams, *ds.XMLID)
+		warnings = append(warnings, warns...)
+
+		// Normal inner behavior has no parent retry strings
+		params.HasRetryParams = hasVals
+	} else { // if serverPlacement.IsFirstCacheTier {
+		hasVals, warns := params.FillParentRetries(ParentConfigRetryKeysFirst, dsParams, *ds.XMLID)
+		warnings = append(warnings, warns...)
+
+		// Normal first behavior has no parent retry strings
+		params.HasRetryParams = hasVals
 	}
 
 	return params, warnings
@@ -919,7 +998,8 @@ func getTopologyParentConfigLine(
 	serverCapabilities map[int]map[ServerCapability]struct{},
 	dsRequiredCapabilities map[int]map[ServerCapability]struct{},
 	cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable,
-	dsParams parentDSParams,
+	profileParentConfigParams map[string]map[string]string,
+	isMSO bool,
 	atsMajorVer int,
 	dsOrigins map[ServerID]struct{},
 	addComments bool,
@@ -935,14 +1015,17 @@ func getTopologyParentConfigLine(
 	}
 
 	serverPlacement, err := getTopologyPlacement(tc.CacheGroupName(*server.Cachegroup), topology, cacheGroups, ds)
-
 	if err != nil {
 		return nil, warnings, errors.New("getting topology placement: " + err.Error())
 	}
+
 	if !serverPlacement.InTopology {
 		return nil, warnings, nil // server isn't in topology, no error
 	}
 
+	dsParams, dswarns := getParentDSParams(*ds, profileParentConfigParams, serverPlacement, isMSO)
+	warnings = append(warnings, dswarns...)
+
 	orgFQDNStr := *ds.OrgServerFQDN
 	// if this cache isn't the last tier, i.e. we're not going to the origin, use http not https
 	if !serverPlacement.IsLastCacheTier {
@@ -954,11 +1037,11 @@ func getTopologyParentConfigLine(
 		return nil, warnings, errors.New("DS '" + *ds.XMLID + "' has malformed origin URI: '" + *ds.OrgServerFQDN + "': skipping!" + err.Error())
 	}
 
-	txt := &ParentAbstractionService{}
-	txt.Name = *ds.XMLID
-	txt.Comment = makeParentComment(addComments, *ds.XMLID, *ds.Topology)
-	txt.DestDomain = orgURI.Hostname()
-	txt.Port, err = strconv.Atoi(orgURI.Port())
+	pasvc := &ParentAbstractionService{}
+	pasvc.Name = *ds.XMLID
+	pasvc.Comment = makeParentComment(addComments, *ds.XMLID, *ds.Topology)
+	pasvc.DestDomain = orgURI.Hostname()
+	pasvc.Port, err = strconv.Atoi(orgURI.Port())
 	if err != nil {
 		return nil, warnings, fmt.Errorf("parent %v port '%v' was not an integer", orgURI, orgURI.Port())
 	}
@@ -966,6 +1049,7 @@ func getTopologyParentConfigLine(
 
 	parents, secondaryParents, parentWarnings, err := getTopologyParents(server, ds, serversWithParams, parentConfigParams, topology, serverPlacement.IsLastTier, serverCapabilities, dsRequiredCapabilities, dsOrigins, dsParams.MergeGroups)
 	warnings = append(warnings, parentWarnings...)
+
 	if err != nil {
 		return nil, warnings, errors.New("getting topology parents for '" + *ds.XMLID + "': skipping! " + err.Error())
 	}
@@ -979,22 +1063,22 @@ func getTopologyParentConfigLine(
 		}
 	}
 
-	txt.Parents = parents
+	pasvc.Parents = parents
 	// txt += ` parent="` + strings.Join(parents, `;`) + `"`
 	if len(secondaryParents) > 0 {
-		txt.SecondaryParents = secondaryParents
+		pasvc.SecondaryParents = secondaryParents
 		// txt += ` secondary_parent="` + strings.Join(secondaryParents, `;`) + `"`
 
 		secondaryModeStr, secondaryModeWarnings := getSecondaryModeStr(dsParams.TryAllPrimariesBeforeSecondary, atsMajorVer, tc.DeliveryServiceName(*ds.XMLID))
 		warnings = append(warnings, secondaryModeWarnings...)
 		// txt += secondaryModeStr
-		txt.SecondaryMode = secondaryModeStr // TODO convert
+		pasvc.SecondaryMode = secondaryModeStr // TODO convert
 	}
 
-	txt.RetryPolicy = getTopologyRoundRobin(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm)
+	pasvc.RetryPolicy = getTopologyRoundRobin(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm)
 	// txt += ` round_robin=` + getTopologyRoundRobin(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm)
 
-	txt.GoDirect = getTopologyGoDirect(ds, serverPlacement.IsLastTier, serverPlacement.IsLastCacheTier)
+	pasvc.GoDirect = getTopologyGoDirect(ds, serverPlacement)
 	// txt += ` go_direct=` + getTopologyGoDirect(ds, serverPlacement.IsLastTier)
 
 	// TODO convert
@@ -1009,7 +1093,7 @@ func getTopologyParentConfigLine(
 	}
 
 	tqWarns := []string{}
-	txt.IgnoreQueryStringInParentSelection, tqWarns = getTopologyQueryStringIgnore(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm, useQueryStringInParentSelection)
+	pasvc.IgnoreQueryStringInParentSelection, tqWarns = getTopologyQueryStringIgnore(ds, serverParams, serverPlacement, dsParams.Algorithm, useQueryStringInParentSelection)
 	warnings = append(warnings, tqWarns...)
 
 	// txt += ` qstring=` + getTopologyQueryString(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm, dsParams.QueryStringHandling)
@@ -1018,23 +1102,22 @@ func getTopologyParentConfigLine(
 	// txt += getTopologyParentIsProxyStr(serverPlacement.IsLastCacheTier)
 
 	// TODO convert
-	prWarns := []string{}
-	txt.MaxSimpleRetries, txt.MaxMarkdownRetries, txt.MarkdownResponseCodes, txt.ErrorResponseCodes, prWarns = getParentRetryStr(serverPlacement.IsLastCacheTier, atsMajorVer, dsParams.ParentRetry, dsParams.UnavailableServerRetryResponses, dsParams.MaxSimpleRetries, dsParams.MaxUnavailableServerRetries)
+	prWarns := dsParams.FillParentSvcRetries(serverPlacement.IsLastCacheTier, atsMajorVer, pasvc)
 	warnings = append(warnings, prWarns...)
 
 	// txt += getParentRetryStr(serverPlacement.IsLastCacheTier, atsMajorVer, dsParams.ParentRetry, dsParams.UnavailableServerRetryResponses, dsParams.MaxSimpleRetries, dsParams.MaxUnavailableServerRetries)
 	// txt += "\n"
 
 	if dsParams.UsePeering {
-		txt.SecondaryMode = ParentAbstractionServiceParentSecondaryModePeering
+		pasvc.SecondaryMode = ParentAbstractionServiceParentSecondaryModePeering
 	}
 
-	return txt, warnings, nil
+	return pasvc, warnings, nil
 }
 
-// getParentRetryStr builds the parent retry directive(s).
+// getParentRetries builds the parent retry directive(s).
 //
-// Returns the MaxSimpleRetries, MaxMarkdownRetries, MarkdownResponseCodes, and ErrorResponseCodes.
+// Returns the MaxSimpleRetries, MaxMarkdownRetries, ErrorREsponseCodes, MarkdownResponseCodes.
 //
 // If atsMajorVer < 6, "" is returned (ATS 5 and below don't support retry directives).
 // If isLastCacheTier is false, "" is returned. This argument exists to simplify usage.
@@ -1045,71 +1128,77 @@ func getTopologyParentConfigLine(
 //
 // Does not return errors. If any input is malformed, warnings are returned and that value is set to -1.
 //
-func getParentRetryStr(isLastCacheTier bool, atsMajorVer int, parentRetry string, unavailableServerRetryResponses string, maxSimpleRetries string, maxUnavailableServerRetries string) (int, int, []int, []int, []string) {
+func (dsparams parentDSParams) FillParentSvcRetries(isLastCacheTier bool, atsMajorVer int, pasvc *ParentAbstractionService) []string {
 	warnings := []string{}
-	if !isLastCacheTier || // allow !isLastCacheTier, to simplify usage.
-		parentRetry == "" || // allow parentRetry to be empty, to simplify usage.
+
+	if !dsparams.HasRetryParams || // allow parentRetry to be empty, to simplify usage.
 		atsMajorVer < 6 { // ATS 5 and below don't support parent_retry directives
 		// warnings = append(warnings, "ATS 5 doesn't support parent retry, not using parent retry values")
-		return -1, -1, nil, nil, warnings // TODO move to formatter?
+		pasvc.MaxSimpleRetries = -1
+		pasvc.MaxMarkdownRetries = -1
+		pasvc.ErrorResponseCodes = nil
+		pasvc.MarkdownResponseCodes = nil
+		return warnings
 	}
 
-	err := error(nil)
+	// Set initial defaults
+	pasvc.MaxSimpleRetries = 0
+	pasvc.MaxMarkdownRetries = 0
+	pasvc.ErrorResponseCodes = []int{}
+	pasvc.MarkdownResponseCodes = []int{}
 
-	maxSimpleRetriesInt := ParentConfigDSParamDefaultMaxSimpleRetries
-	if maxSimpleRetries != "" {
-		maxSimpleRetriesInt, err = strconv.Atoi(maxSimpleRetries)
-		if err != nil {
-			maxSimpleRetriesInt = ParentConfigDSParamDefaultMaxSimpleRetries
-			warnings = append(warnings, "malformed maxSimpleRetries '"+maxSimpleRetries+"', using default "+strconv.Itoa(maxSimpleRetriesInt))
+	if isLastCacheTier {
+		pasvc.MaxSimpleRetries = ParentConfigDSParamDefaultMaxSimpleRetries
+		pasvc.MaxMarkdownRetries = ParentConfigDSParamDefaultMaxUnavailableServerRetries
+	}
+
+	if dsparams.MaxSimpleRetries != "" {
+		if retriesint, err := strconv.Atoi(dsparams.MaxSimpleRetries); err == nil {
+			pasvc.MaxSimpleRetries = retriesint
+		} else {
+			warnings = append(warnings, "malformed maxSimpleRetries '"+dsparams.MaxSimpleRetries+"', using default "+strconv.Itoa(pasvc.MaxSimpleRetries))
 		}
 	}
 
-	maxUnavailableServerRetriesInt := ParentConfigDSParamDefaultMaxUnavailableServerRetries
-	if maxUnavailableServerRetries != "" {
-		maxUnavailableServerRetriesInt, err = strconv.Atoi(maxUnavailableServerRetries)
-		if err != nil {
-			maxUnavailableServerRetriesInt = ParentConfigDSParamDefaultMaxUnavailableServerRetries
-			warnings = append(warnings, "malformed maxUnavailableServerRetries '"+maxUnavailableServerRetries+"', using default "+strconv.Itoa(maxUnavailableServerRetriesInt))
+	if dsparams.MaxUnavailableServerRetries != "" {
+		if retriesint, err := strconv.Atoi(dsparams.MaxUnavailableServerRetries); err == nil {
+			pasvc.MaxMarkdownRetries = retriesint
+		} else {
+			warnings = append(warnings, "malformed maxUnavailableServerRetries '"+dsparams.MaxUnavailableServerRetries+"', using default "+strconv.Itoa(pasvc.MaxMarkdownRetries))
 		}
 	}
 
-	unavailableServerRetryResponsesInts, err := ParseRetryResponses(unavailableServerRetryResponses)
-	if err != nil {
-		warnings = append(warnings, "malformed unavailableServerRetryResponses '"+unavailableServerRetryResponses+"', using default")
-		unavailableServerRetryResponsesInts = []int{}
+	// simple retry responses only supported int ATS for 9.1.x and above
+	if simpleResponseInts, err := ParseRetryResponses(dsparams.SimpleServerRetryResponses); err == nil {
+		pasvc.ErrorResponseCodes = simpleResponseInts
+	} else {
+		warnings = append(warnings, "malformed simpleServerRetryResponses '"+dsparams.SimpleServerRetryResponses+"', using default")
 	}
 
-	simpleRetryResponsesInts := []int{}
-	// TODO add support for 9.1
-	// simpleRetryResponsesInts, err := ParseRetryResponses(simpleRetryResponses)
-	// if err != nil {
-	// 	warnings = append(warnings, "malformed simpleRetryResponses '"+simpleRetryResponses+"', using default")
-	// 	simpleRetryResponsesInts = []int{}
-	// }
+	if unavailResponseInts, err := ParseRetryResponses(dsparams.UnavailableServerRetryResponses); err == nil {
+		pasvc.MarkdownResponseCodes = unavailResponseInts
+	} else {
+		warnings = append(warnings, "malformed unavailableServerRetryResponses '"+dsparams.UnavailableServerRetryResponses+"', using default")
+	}
 
 	// TODO make consts
-	switch strings.ToLower(strings.TrimSpace(parentRetry)) {
+	switch strings.ToLower(strings.TrimSpace(dsparams.ParentRetry)) {
 	case "simple_retry":
-		unavailableServerRetryResponsesInts = []int{}
-		if len(simpleRetryResponsesInts) == 0 {
-			simpleRetryResponsesInts = append(simpleRetryResponsesInts, DefaultSimpleRetryCodes...)
+		if len(pasvc.ErrorResponseCodes) == 0 {
+			pasvc.ErrorResponseCodes = DefaultSimpleRetryCodes
 		}
 	case "unavailable_server_retry":
-		simpleRetryResponsesInts = []int{}
-		if len(unavailableServerRetryResponsesInts) == 0 {
-			unavailableServerRetryResponsesInts = append(unavailableServerRetryResponsesInts, DefaultUnavailableServerRetryCodes...)
+		if len(pasvc.MarkdownResponseCodes) == 0 {
+			pasvc.MarkdownResponseCodes = DefaultUnavailableServerRetryCodes
 		}
 	case "both":
-		if len(unavailableServerRetryResponsesInts) == 0 {
-			unavailableServerRetryResponsesInts = append(unavailableServerRetryResponsesInts, DefaultUnavailableServerRetryCodes...)
+		if len(pasvc.ErrorResponseCodes) == 0 {
+			pasvc.ErrorResponseCodes = DefaultSimpleRetryCodes
 		}
-		if len(simpleRetryResponsesInts) == 0 {
-			simpleRetryResponsesInts = append(simpleRetryResponsesInts, DefaultSimpleRetryCodes...)
+		if len(pasvc.MarkdownResponseCodes) == 0 {
+			pasvc.MarkdownResponseCodes = DefaultUnavailableServerRetryCodes
 		}
 	default:
-		unavailableServerRetryResponsesInts = []int{}
-		simpleRetryResponsesInts = []int{}
 	}
 
 	// txt := ` parent_retry=` + parentRetry
@@ -1117,7 +1206,7 @@ func getParentRetryStr(isLastCacheTier bool, atsMajorVer int, parentRetry string
 	// 	txt += ` unavailable_server_retry_responses=` + unavailableServerRetryResponses
 	// }
 	// txt += ` max_simple_retries=` + maxSimpleRetries + ` max_unavailable_server_retries=` + maxUnavailableServerRetries
-	return maxSimpleRetriesInt, maxUnavailableServerRetriesInt, unavailableServerRetryResponsesInts, simpleRetryResponsesInts, warnings
+	return warnings
 }
 
 // getSecondaryModeStr returns the secondary_mode string, and any warnings.
@@ -1127,7 +1216,7 @@ func getSecondaryModeStr(tryAllPrimariesBeforeSecondary bool, atsMajorVer int, d
 		return ParentAbstractionServiceParentSecondaryModeDefault, warnings
 	}
 	if atsMajorVer < 8 {
-		warnings = append(warnings, "DS '"+string(ds)+"' had Parameter "+ParentConfigParamSecondaryMode+" but this cache is "+strconv.Itoa(atsMajorVer)+" and secondary_mode isn't supported in ATS until 8. Not using!")
+		warnings = append(warnings, "DS '"+string(ds)+"' had Parameter "+ParentConfigRetryKeysDefault.SecondaryMode+" but this cache is "+strconv.Itoa(atsMajorVer)+" and secondary_mode isn't supported in ATS until 8. Not using!")
 		return ParentAbstractionServiceParentSecondaryModeDefault, warnings
 	}
 
@@ -1152,7 +1241,7 @@ func getTopologyRoundRobin(
 	if !serverIsLastTier {
 		return ParentAbstractionServiceRetryPolicyConsistentHash
 	}
-	if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; ds.OriginShield != nil && *ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
+	if parentSelectAlg := serverParams[ParentConfigRetryKeysDefault.Algorithm]; ds.OriginShield != nil && *ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
 		if policy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(parentSelectAlg); policy != ParentAbstractionServiceRetryPolicyInvalid {
 			return policy
 		}
@@ -1163,17 +1252,14 @@ func getTopologyRoundRobin(
 	return ParentAbstractionServiceRetryPolicyConsistentHash
 }
 
-func getTopologyGoDirect(ds *DeliveryService, serverIsLastTier bool, serverIsLastCacheTier bool) bool {
-	if serverIsLastCacheTier {
+func getTopologyGoDirect(ds *DeliveryService, serverPlacement TopologyPlacement) bool {
+	if serverPlacement.IsLastCacheTier {
 		return true
-	}
-	if !serverIsLastTier {
+	} else if !serverPlacement.IsLastTier {
 		return false
-	}
-	if ds.OriginShield != nil && *ds.OriginShield != "" {
+	} else if ds.OriginShield != nil && *ds.OriginShield != "" {
 		return true
-	}
-	if ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin {
+	} else if ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin {
 		return false
 	}
 	return true
@@ -1182,12 +1268,12 @@ func getTopologyGoDirect(ds *DeliveryService, serverIsLastTier bool, serverIsLas
 func getTopologyQueryStringIgnore(
 	ds *DeliveryService,
 	serverParams map[string]string,
-	serverIsLastTier bool,
+	serverPlacement TopologyPlacement,
 	algorithm ParentAbstractionServiceRetryPolicy,
 	qStringHandling *bool,
 ) (bool, []string) {
 	warnings := []string{}
-	if serverIsLastTier {
+	if serverPlacement.IsLastCacheTier {
 		if ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin && qStringHandling == nil && algorithm == ParentAbstractionServiceRetryPolicyConsistentHash && ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp {
 			return false, warnings
 		}
@@ -1845,7 +1931,7 @@ func getServerParentConfigParams(server *Server, allParentConfigParams []paramet
 		name := pa.Name
 		val := pa.Value
 		if name == ParentConfigParamQStringHandling ||
-			name == ParentConfigParamAlgorithm ||
+			name == ParentConfigRetryKeysDefault.Algorithm ||
 			name == ParentConfigParamQString {
 			serverParams[name] = val
 		}
diff --git a/lib/go-atscfg/parentdotconfig_test.go b/lib/go-atscfg/parentdotconfig_test.go
index 239fed2b7a..25595c37c9 100644
--- a/lib/go-atscfg/parentdotconfig_test.go
+++ b/lib/go-atscfg/parentdotconfig_test.go
@@ -53,7 +53,7 @@ func TestMakeParentDotConfig(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -164,7 +164,7 @@ func TestMakeParentDotConfigCapabilities(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -308,7 +308,7 @@ func TestMakeParentDotConfigMSOSecondaryParent(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -402,6 +402,10 @@ func TestMakeParentDotConfigMSOSecondaryParent(t *testing.T) {
 	if !strings.Contains(txtx, `secondary_parent="my-parent-1.my-parent-1-domain`) {
 		t.Errorf("expected secondary parent 'my-parent-1.my-parent-1-domain', actual: '%v'", txt)
 	}
+
+	if strings.Contains(txtx, "parent_retry") {
+		t.Errorf("Did not expect parent_retry parameter at edge/inner: '%v'", txt)
+	}
 }
 
 func TestMakeParentDotConfigMSONoPrimaryParent(t *testing.T) {
@@ -423,7 +427,7 @@ func TestMakeParentDotConfigMSONoPrimaryParent(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -547,7 +551,7 @@ func TestMakeParentDotConfigTopologies(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -689,7 +693,7 @@ func TestMakeParentDotConfigNotInTopologies(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -829,7 +833,7 @@ func TestMakeParentDotConfigTopologiesCapabilities(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -977,7 +981,7 @@ func TestMakeParentDotConfigTopologiesOmitOfflineParents(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -1120,7 +1124,7 @@ func TestMakeParentDotConfigTopologiesOmitDifferentCDNParents(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -1254,7 +1258,7 @@ func TestMakeParentDotConfigTopologiesMSO(t *testing.T) {
 
 	parentConfigParams := []tc.Parameter{
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -1445,7 +1449,7 @@ func TestMakeParentDotConfigTopologiesMSOWithCapabilities(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -1585,7 +1589,7 @@ func TestMakeParentDotConfigMSOWithCapabilities(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -1715,7 +1719,7 @@ func TestMakeParentDotConfigTopologiesMSOParams(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -1727,33 +1731,33 @@ func TestMakeParentDotConfigTopologiesMSOParams(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMSOAlgorithm,
+			Name:       ParentConfigRetryKeysMSO.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      "consistent_hash",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMSOParentRetry,
+			Name:       ParentConfigRetryKeysMSO.ParentRetry,
 			ConfigFile: "parent.config",
 			Value:      "both",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMSOUnavailableServerRetryResponses,
+			Name:       ParentConfigRetryKeysMSO.MaxSimpleRetries,
 			ConfigFile: "parent.config",
-			Value:      `"400,503"`,
+			Value:      "14",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMSOMaxSimpleRetries,
+			Name:       ParentConfigRetryKeysMSO.MaxUnavailableRetries,
 			ConfigFile: "parent.config",
-			Value:      "14",
+			Value:      "9",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMSOMaxUnavailableServerRetries,
+			Name:       ParentConfigRetryKeysMSO.UnavailableRetryResponses,
 			ConfigFile: "parent.config",
-			Value:      "9",
+			Value:      `"400,503"`,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 	}
@@ -1891,7 +1895,7 @@ func TestMakeParentDotConfigTopologiesParams(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -1903,33 +1907,33 @@ func TestMakeParentDotConfigTopologiesParams(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      "consistent_hash",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamParentRetry,
+			Name:       ParentConfigRetryKeysDefault.ParentRetry,
 			ConfigFile: "parent.config",
 			Value:      "both",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamUnavailableServerRetryResponses,
+			Name:       ParentConfigRetryKeysDefault.MaxSimpleRetries,
 			ConfigFile: "parent.config",
-			Value:      `"400,503"`,
+			Value:      "14",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxSimpleRetries,
+			Name:       ParentConfigRetryKeysDefault.MaxUnavailableRetries,
 			ConfigFile: "parent.config",
-			Value:      "14",
+			Value:      "9",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxUnavailableServerRetries,
+			Name:       ParentConfigRetryKeysDefault.UnavailableRetryResponses,
 			ConfigFile: "parent.config",
-			Value:      "9",
+			Value:      `"400,503"`,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 	}
@@ -2070,7 +2074,7 @@ func TestMakeParentDotConfigTopologiesNonStandardServerTypes(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -2218,7 +2222,7 @@ func TestMakeParentDotConfigSecondaryMode(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -2230,7 +2234,13 @@ func TestMakeParentDotConfigSecondaryMode(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamSecondaryMode,
+			Name:       ParentConfigRetryKeysDefault.SecondaryMode,
+			ConfigFile: "parent.config",
+			Value:      "",
+			Profiles:   []byte(`["ds0Profile","ds1Profile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigRetryKeysFirst.SecondaryMode,
 			ConfigFile: "parent.config",
 			Value:      "",
 			Profiles:   []byte(`["ds0Profile","ds1Profile"]`),
@@ -2379,7 +2389,7 @@ func TestMakeParentDotConfigNoSecondaryMode(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -2536,7 +2546,7 @@ func TestMakeParentDotConfigComments(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -2662,7 +2672,7 @@ func TestMakeParentDotConfigCommentTopology(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -2816,7 +2826,7 @@ func TestMakeParentDotConfigHTTPSOrigin(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -2933,7 +2943,7 @@ func TestMakeParentDotConfigHTTPSOriginTopology(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -3067,7 +3077,7 @@ func TestMakeParentDotConfigNoParentNoTopology(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -3196,7 +3206,7 @@ func TestMakeParentDotConfigHTTPSOriginTopologyNoPrimaryParent(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -3334,7 +3344,7 @@ func TestMakeParentDotConfigMergeParentGroupTopology(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -3543,7 +3553,7 @@ func TestMakeParentDotConfigTopologiesServerMultipleProfileParams(t *testing.T)
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -3555,33 +3565,33 @@ func TestMakeParentDotConfigTopologiesServerMultipleProfileParams(t *testing.T)
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      "consistent_hash",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamParentRetry,
+			Name:       ParentConfigRetryKeysDefault.ParentRetry,
 			ConfigFile: "parent.config",
 			Value:      "both",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamUnavailableServerRetryResponses,
+			Name:       ParentConfigRetryKeysDefault.MaxSimpleRetries,
 			ConfigFile: "parent.config",
-			Value:      `"400,503"`,
+			Value:      "14",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxSimpleRetries,
+			Name:       ParentConfigRetryKeysDefault.MaxUnavailableRetries,
 			ConfigFile: "parent.config",
-			Value:      "14",
+			Value:      "9",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxUnavailableServerRetries,
+			Name:       ParentConfigRetryKeysDefault.UnavailableRetryResponses,
 			ConfigFile: "parent.config",
-			Value:      "9",
+			Value:      `"400,503"`,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
@@ -3710,6 +3720,745 @@ func TestMakeParentDotConfigTopologiesServerMultipleProfileParams(t *testing.T)
 	}
 }
 
+func TestMakeParentDotConfigFirstLastNoTopo(t *testing.T) {
+	hdr := &ParentConfigOpts{AddComments: true, HdrComment: "myHeaderComment"}
+
+	// Non Toplogy ds
+	ds0 := makeParentDS()
+	ds0Type := tc.DSTypeHTTP
+	ds0.Type = &ds0Type
+	ds0.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreUseInCacheKeyAndPassUp))
+	ds0.OrgServerFQDN = util.StrPtr("https://ds0.example.net")
+	ds0.ProfileID = util.IntPtr(311)
+	ds0.ProfileName = util.StrPtr("ds0Profile")
+
+	// Non Toplogy ds, MSO
+	ds1 := makeParentDS()
+	ds1Type := tc.DSTypeHTTP
+	ds1.Type = &ds1Type
+	ds1.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreUseInCacheKeyAndPassUp))
+	ds1.OrgServerFQDN = util.StrPtr("https://ds1.example.net")
+	ds1.ProfileID = util.IntPtr(312)
+	ds1.ProfileName = util.StrPtr("ds0Profile")
+	ds1.MultiSiteOrigin = util.BoolPtr(true)
+
+	dses := []DeliveryService{*ds0, *ds1}
+
+	parentConfigParams := []tc.Parameter{
+		{
+			Name:       ParentConfigParamQStringHandling,
+			ConfigFile: "parent.config",
+			Value:      "myQStringHandlingParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		{
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
+			ConfigFile: "parent.config",
+			Value:      tc.AlgorithmConsistentHash,
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		{
+			Name:       ParentConfigParamQString,
+			ConfigFile: "parent.config",
+			Value:      "myQstringParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+	}
+
+	// Create set of DS params
+	params := map[string]string{
+		ParentConfigRetryKeysDefault.Algorithm: "strict",
+		ParentConfigRetryKeysMSO.Algorithm:     "strict",
+		ParentConfigRetryKeysFirst.Algorithm:   "true",
+		ParentConfigRetryKeysInner.Algorithm:   "latched",
+		ParentConfigRetryKeysLast.Algorithm:    "true",
+
+		ParentConfigRetryKeysDefault.SecondaryMode: "exhaust",
+		ParentConfigRetryKeysMSO.SecondaryMode:     "exhaust",
+		ParentConfigRetryKeysFirst.SecondaryMode:   "alternate",
+		ParentConfigRetryKeysInner.SecondaryMode:   "peering",
+		ParentConfigRetryKeysLast.SecondaryMode:    "alternate",
+
+		ParentConfigRetryKeysMSO.ParentRetry:   "unavailable_server_retry",
+		ParentConfigRetryKeysFirst.ParentRetry: "both",
+		ParentConfigRetryKeysInner.ParentRetry: "both",
+		ParentConfigRetryKeysLast.ParentRetry:  "both",
+
+		ParentConfigRetryKeysDefault.MaxSimpleRetries: "11",
+		ParentConfigRetryKeysMSO.MaxSimpleRetries:     "11",
+		ParentConfigRetryKeysFirst.MaxSimpleRetries:   "12",
+		ParentConfigRetryKeysInner.MaxSimpleRetries:   "13",
+		ParentConfigRetryKeysLast.MaxSimpleRetries:    "14",
+
+		ParentConfigRetryKeysDefault.SimpleRetryResponses: `"401"`,
+		ParentConfigRetryKeysMSO.SimpleRetryResponses:     `"401"`,
+		ParentConfigRetryKeysFirst.SimpleRetryResponses:   `"401,402"`,
+		ParentConfigRetryKeysInner.SimpleRetryResponses:   `"401,403"`,
+		ParentConfigRetryKeysLast.SimpleRetryResponses:    `"401,404"`,
+
+		ParentConfigRetryKeysDefault.MaxUnavailableRetries: "21",
+		ParentConfigRetryKeysMSO.MaxUnavailableRetries:     "21",
+		ParentConfigRetryKeysFirst.MaxUnavailableRetries:   "22",
+		ParentConfigRetryKeysInner.MaxUnavailableRetries:   "23",
+		ParentConfigRetryKeysLast.MaxUnavailableRetries:    "24",
+
+		ParentConfigRetryKeysDefault.UnavailableRetryResponses: `"501"`,
+		ParentConfigRetryKeysMSO.UnavailableRetryResponses:     `"501"`,
+		ParentConfigRetryKeysFirst.UnavailableRetryResponses:   `"501,502"`,
+		ParentConfigRetryKeysInner.UnavailableRetryResponses:   `"501,503"`,
+		ParentConfigRetryKeysLast.UnavailableRetryResponses:    `"501,504"`,
+	}
+
+	// Assign them to the profile
+	for key, val := range params {
+		tcparam := tc.Parameter{
+			Name:       key,
+			ConfigFile: "parent.config",
+			Value:      val,
+			Profiles:   []byte(`["ds0Profile"]`),
+		}
+		parentConfigParams = append(parentConfigParams, tcparam)
+	}
+
+	serverParams := []tc.Parameter{
+		{
+			Name:       "trafficserver",
+			ConfigFile: "package",
+			Value:      "9",
+			Profiles:   []byte(`["global"]`),
+		},
+	}
+
+	edge := makeTestParentServer()
+	edge.Cachegroup = util.StrPtr("edgeCG")
+	edge.CachegroupID = util.IntPtr(400)
+
+	mid0 := makeTestParentServer()
+	mid0.Cachegroup = util.StrPtr("midCG0")
+	mid0.CachegroupID = util.IntPtr(500)
+	mid0.HostName = util.StrPtr("mymid0")
+	mid0.ID = util.IntPtr(45)
+	setIP(mid0, "192.168.2.2")
+
+	mid1 := makeTestParentServer()
+	mid1.Cachegroup = util.StrPtr("midCG1")
+	mid1.CachegroupID = util.IntPtr(501)
+	mid1.HostName = util.StrPtr("mymid1")
+	mid1.ID = util.IntPtr(46)
+	setIP(mid0, "192.168.2.3")
+
+	org0 := makeTestParentServer()
+	org0.Cachegroup = util.StrPtr("orgCG0")
+	org0.CachegroupID = util.IntPtr(502)
+	org0.HostName = util.StrPtr("myorg0")
+	org0.ID = util.IntPtr(47)
+	setIP(org0, "192.168.2.4")
+	org0.Type = tc.OriginTypeName
+	org0.TypeID = util.IntPtr(991)
+
+	org1 := makeTestParentServer()
+	org1.Cachegroup = util.StrPtr("orgCG1")
+	org1.CachegroupID = util.IntPtr(503)
+	org1.HostName = util.StrPtr("myorg1")
+	org1.ID = util.IntPtr(48)
+	setIP(org1, "192.168.2.5")
+	org1.Type = tc.OriginTypeName
+	org1.TypeID = util.IntPtr(991)
+
+	servers := []Server{*edge, *mid0, *mid1, *org0, *org1}
+
+	topologies := []tc.Topology{
+		{
+			Name: "t0",
+			Nodes: []tc.TopologyNode{
+				{
+					Cachegroup: "edgeCG",
+					Parents:    []int{1, 2},
+				},
+				{
+					Cachegroup: "midCG0",
+					Parents:    []int{3, 4},
+				},
+				{
+					Cachegroup: "midCG1",
+					Parents:    []int{3, 4},
+				},
+				{
+					Cachegroup: "orgCG0",
+				},
+				{
+					Cachegroup: "orgCG1",
+				},
+			},
+		},
+	}
+
+	serverCapabilities := map[int]map[ServerCapability]struct{}{}
+	dsRequiredCapabilities := map[int]map[ServerCapability]struct{}{}
+
+	eCG := &tc.CacheGroupNullable{}
+	eCG.Name = edge.Cachegroup
+	eCG.ID = edge.CachegroupID
+	eCG.ParentName = mid0.Cachegroup
+	eCG.ParentCachegroupID = mid0.CachegroupID
+	eCG.SecondaryParentName = mid1.Cachegroup
+	eCG.SecondaryParentCachegroupID = mid1.CachegroupID
+	eCGType := tc.CacheGroupEdgeTypeName
+	eCG.Type = &eCGType
+
+	mCG0 := &tc.CacheGroupNullable{}
+	mCG0.Name = mid0.Cachegroup
+	mCG0.ID = mid0.CachegroupID
+	mCG0.ParentName = org0.Cachegroup
+	mCG0.ParentCachegroupID = org0.CachegroupID
+	mCG0.SecondaryParentName = org1.Cachegroup
+	mCG0.SecondaryParentCachegroupID = org1.CachegroupID
+	mCGType0 := tc.CacheGroupMidTypeName
+	mCG0.Type = &mCGType0
+
+	mCG1 := &tc.CacheGroupNullable{}
+	mCG1.Name = mid1.Cachegroup
+	mCG1.ID = mid1.CachegroupID
+	mCG1.ParentName = org1.Cachegroup
+	mCG1.ParentCachegroupID = org1.CachegroupID
+	mCG1.SecondaryParentName = org0.Cachegroup
+	mCG1.SecondaryParentCachegroupID = org0.CachegroupID
+	mCGType1 := tc.CacheGroupMidTypeName
+	mCG1.Type = &mCGType1
+
+	oCG0 := &tc.CacheGroupNullable{}
+	oCG0.Name = org0.Cachegroup
+	oCG0.ID = org0.CachegroupID
+	oCGType0 := tc.CacheGroupOriginTypeName
+	oCG0.Type = &oCGType0
+
+	oCG1 := &tc.CacheGroupNullable{}
+	oCG1.Name = org1.Cachegroup
+	oCG1.ID = org1.CachegroupID
+	oCGType1 := tc.CacheGroupOriginTypeName
+	oCG1.Type = &oCGType1
+
+	cgs := []tc.CacheGroupNullable{*eCG, *mCG0, *mCG1, *oCG0, *oCG1}
+
+	dss := []DeliveryServiceServer{
+		{Server: *edge.ID, DeliveryService: *ds0.ID},
+		{Server: *mid0.ID, DeliveryService: *ds0.ID},
+		{Server: *mid1.ID, DeliveryService: *ds0.ID},
+		{Server: *org0.ID, DeliveryService: *ds0.ID},
+		{Server: *org1.ID, DeliveryService: *ds0.ID},
+
+		{Server: *edge.ID, DeliveryService: *ds1.ID},
+		{Server: *mid0.ID, DeliveryService: *ds1.ID},
+		{Server: *mid1.ID, DeliveryService: *ds1.ID},
+		{Server: *org0.ID, DeliveryService: *ds1.ID},
+		{Server: *org1.ID, DeliveryService: *ds1.ID},
+	}
+	cdn := &tc.CDN{
+		DomainName: "cdndomain.example",
+		Name:       "my-cdn-name",
+	}
+
+	dsstrs := []string{
+		`dest_domain=ds0.example.net `,
+		`dest_domain=ds1.example.net `,
+	}
+
+	{ // test edge config
+		cfg, err := MakeParentDotConfig(dses, edge, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, hdr)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, hdr.HdrComment)
+
+		needs := []string{
+			` secondary_mode=2`,
+			` round_robin=consistent_hash`,
+			` go_direct=false`,
+			` parent_is_proxy=true`,
+			` parent_retry=both`,
+			` max_simple_retries=12`,
+			` max_unavailable_server_retries=22`,
+			` simple_server_retry_responses="401,402"`,
+			` unavailable_server_retry_responses="501,502"`,
+		}
+
+		for _, dsstr := range dsstrs {
+			cnt := strings.Count(txt, dsstr)
+			if 1 != cnt {
+				t.Errorf("Expected one entry for %s got %d\n%v", dsstr, cnt, txt)
+			} else {
+				lines := strings.Split(txt, "\n")
+				dsline := lineWhichContains(lines, dsstr)
+				missing := missingFrom(dsline, needs)
+				if 0 < len(missing) {
+					t.Errorf("Missing required string(s) from line: %v\n%v", missing, dsline)
+				}
+			}
+		}
+	}
+
+	{ // test mid config
+		cfg, err := MakeParentDotConfig(dses, mid0, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, hdr)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, hdr.HdrComment)
+
+		needs := []string{
+			` round_robin=true`,
+			` go_direct=true`,
+			` parent_is_proxy=false`,
+			` parent_retry=both`,
+			` max_simple_retries=12`,
+			` max_unavailable_server_retries=22`,
+			` simple_server_retry_responses="401,402"`,
+			` unavailable_server_retry_responses="501,502"`,
+		}
+
+		{
+			dsstr := "dest_domain=ds1.example.net"
+			cnt := strings.Count(txt, dsstr)
+			if 1 != cnt {
+				t.Errorf("Expected one entry for %s got %d\n%v", dsstr, cnt, txt)
+			} else {
+				lines := strings.Split(txt, "\n")
+				dsline := lineWhichContains(lines, dsstr)
+				missing := missingFrom(dsline, needs)
+				if 0 < len(missing) {
+					t.Errorf("Missing required string(s) from line: %v\n%v", missing, dsline)
+				}
+			}
+		}
+	}
+}
+
+func TestMakeParentDotConfigFirstInnerLastTopology(t *testing.T) {
+	hdr := &ParentConfigOpts{AddComments: true, HdrComment: "myHeaderComment"}
+
+	// Toplogy ds, MSO
+	ds0 := makeParentDS()
+	ds0Type := tc.DSTypeHTTP
+	ds0.Type = &ds0Type
+	ds0.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreUseInCacheKeyAndPassUp))
+	ds0.OrgServerFQDN = util.StrPtr("http://ds0.example.net")
+	ds0.ProfileID = util.IntPtr(311)
+	ds0.ProfileName = util.StrPtr("ds0Profile")
+	ds0.MultiSiteOrigin = util.BoolPtr(true)
+	ds0.Topology = util.StrPtr("t0")
+
+	// Toplogy ds, non MSO
+	ds1 := makeParentDS()
+	ds1.ID = util.IntPtr(44)
+	ds1Type := tc.DSTypeHTTP
+	ds1.Type = &ds1Type
+	ds1.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreUseInCacheKeyAndPassUp))
+	ds1.OrgServerFQDN = util.StrPtr("http://ds1.example.net")
+	ds1.ProfileID = util.IntPtr(311)
+	ds1.ProfileName = util.StrPtr("ds0Profile")
+	ds1.Topology = util.StrPtr("t0")
+
+	dses := []DeliveryService{*ds0, *ds1}
+
+	parentConfigParams := []tc.Parameter{
+		{
+			Name:       ParentConfigParamQStringHandling,
+			ConfigFile: "parent.config",
+			Value:      "myQStringHandlingParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		{
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
+			ConfigFile: "parent.config",
+			Value:      tc.AlgorithmConsistentHash,
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		{
+			Name:       ParentConfigParamQString,
+			ConfigFile: "parent.config",
+			Value:      "myQstringParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+	}
+
+	// Create set of DS params
+	params := map[string]string{
+		ParentConfigRetryKeysDefault.Algorithm: "strict",
+		ParentConfigRetryKeysMSO.Algorithm:     "strict",
+		ParentConfigRetryKeysFirst.Algorithm:   "true",
+		ParentConfigRetryKeysInner.Algorithm:   "latched",
+		ParentConfigRetryKeysLast.Algorithm:    "true",
+
+		ParentConfigRetryKeysDefault.SecondaryMode: "exhaust",
+		ParentConfigRetryKeysMSO.SecondaryMode:     "exhaust",
+		ParentConfigRetryKeysFirst.SecondaryMode:   "alternate",
+		ParentConfigRetryKeysInner.SecondaryMode:   "peering",
+		ParentConfigRetryKeysLast.SecondaryMode:    "alternate",
+
+		ParentConfigRetryKeysMSO.ParentRetry:   "unavailable_server_retry",
+		ParentConfigRetryKeysFirst.ParentRetry: "both",
+		ParentConfigRetryKeysInner.ParentRetry: "both",
+		ParentConfigRetryKeysLast.ParentRetry:  "both",
+
+		ParentConfigRetryKeysDefault.MaxSimpleRetries: "11",
+		ParentConfigRetryKeysMSO.MaxSimpleRetries:     "11",
+		ParentConfigRetryKeysFirst.MaxSimpleRetries:   "12",
+		ParentConfigRetryKeysInner.MaxSimpleRetries:   "13",
+		ParentConfigRetryKeysLast.MaxSimpleRetries:    "14",
+
+		ParentConfigRetryKeysDefault.SimpleRetryResponses: `"401"`,
+		ParentConfigRetryKeysMSO.SimpleRetryResponses:     `"401"`,
+		ParentConfigRetryKeysFirst.SimpleRetryResponses:   `"401,402"`,
+		ParentConfigRetryKeysInner.SimpleRetryResponses:   `"401,403"`,
+		ParentConfigRetryKeysLast.SimpleRetryResponses:    `"401,404"`,
+
+		ParentConfigRetryKeysDefault.MaxUnavailableRetries: "21",
+		ParentConfigRetryKeysMSO.MaxUnavailableRetries:     "21",
+		ParentConfigRetryKeysFirst.MaxUnavailableRetries:   "22",
+		ParentConfigRetryKeysInner.MaxUnavailableRetries:   "23",
+		ParentConfigRetryKeysLast.MaxUnavailableRetries:    "24",
+
+		ParentConfigRetryKeysDefault.UnavailableRetryResponses: `"501"`,
+		ParentConfigRetryKeysMSO.UnavailableRetryResponses:     `"501"`,
+		ParentConfigRetryKeysFirst.UnavailableRetryResponses:   `"501,502"`,
+		ParentConfigRetryKeysInner.UnavailableRetryResponses:   `"501,503"`,
+		ParentConfigRetryKeysLast.UnavailableRetryResponses:    `"501,504"`,
+	}
+
+	// Assign them to the profile
+	for key, val := range params {
+		tcparam := tc.Parameter{
+			Name:       key,
+			ConfigFile: "parent.config",
+			Value:      val,
+			Profiles:   []byte(`["ds0Profile"]`),
+		}
+		parentConfigParams = append(parentConfigParams, tcparam)
+	}
+
+	serverParams := []tc.Parameter{
+		{
+			Name:       "trafficserver",
+			ConfigFile: "package",
+			Value:      "9",
+			Profiles:   []byte(`["global"]`),
+		},
+	}
+
+	edge := makeTestParentServer()
+	edge.Cachegroup = util.StrPtr("edgeCG")
+	edge.CachegroupID = util.IntPtr(400)
+	edge.HostName = util.StrPtr("edge")
+
+	mid0 := makeTestParentServer()
+	mid0.Cachegroup = util.StrPtr("midCG0")
+	mid0.CachegroupID = util.IntPtr(500)
+	mid0.HostName = util.StrPtr("mid0")
+	mid0.ID = util.IntPtr(45)
+	setIP(mid0, "192.168.2.2")
+
+	mid1 := makeTestParentServer()
+	mid1.Cachegroup = util.StrPtr("midCG1")
+	mid1.CachegroupID = util.IntPtr(501)
+	mid1.HostName = util.StrPtr("mid1")
+	mid1.ID = util.IntPtr(46)
+	setIP(mid1, "192.168.2.3")
+
+	opl0 := makeTestParentServer()
+	opl0.Cachegroup = util.StrPtr("oplCG0")
+	opl0.CachegroupID = util.IntPtr(502)
+	opl0.HostName = util.StrPtr("opl0")
+	opl0.ID = util.IntPtr(47)
+	setIP(opl0, "192.168.2.4")
+
+	opl1 := makeTestParentServer()
+	opl1.Cachegroup = util.StrPtr("oplCG1")
+	opl1.CachegroupID = util.IntPtr(503)
+	opl1.HostName = util.StrPtr("opl1")
+	opl1.ID = util.IntPtr(48)
+	setIP(opl1, "192.168.2.5")
+
+	org0 := makeTestParentServer()
+	org0.Cachegroup = util.StrPtr("orgCG0")
+	org0.CachegroupID = util.IntPtr(504)
+	org0.HostName = util.StrPtr("org0")
+	org0.ID = util.IntPtr(49)
+	setIP(org0, "192.168.2.6")
+	org0.Type = tc.OriginTypeName
+	org0.TypeID = util.IntPtr(991)
+
+	org1 := makeTestParentServer()
+	org1.Cachegroup = util.StrPtr("orgCG1")
+	org1.CachegroupID = util.IntPtr(505)
+	org1.HostName = util.StrPtr("org1")
+	org1.ID = util.IntPtr(50)
+	setIP(org1, "192.168.2.7")
+	org1.Type = tc.OriginTypeName
+	org1.TypeID = util.IntPtr(991)
+
+	servers := []Server{*edge, *mid0, *mid1, *opl0, *opl1, *org0, *org1}
+
+	topologies := []tc.Topology{
+		{
+			Name: "t0",
+			Nodes: []tc.TopologyNode{
+				{
+					Cachegroup: "edgeCG",
+					Parents:    []int{1, 2},
+				},
+				{
+					Cachegroup: "midCG0",
+					Parents:    []int{3, 4},
+				},
+				{
+					Cachegroup: "midCG1",
+					Parents:    []int{3, 4},
+				},
+				{
+					Cachegroup: "oplCG0",
+					Parents:    []int{5, 6},
+				},
+				{
+					Cachegroup: "oplCG1",
+					Parents:    []int{5, 6},
+				},
+				{
+					Cachegroup: "orgCG0",
+				},
+				{
+					Cachegroup: "orgCG1",
+				},
+			},
+		},
+	}
+
+	serverCapabilities := map[int]map[ServerCapability]struct{}{}
+	dsRequiredCapabilities := map[int]map[ServerCapability]struct{}{}
+
+	eCG := &tc.CacheGroupNullable{}
+	eCG.Name = edge.Cachegroup
+	eCG.ID = edge.CachegroupID
+	eCG.ParentName = mid0.Cachegroup
+	eCG.ParentCachegroupID = mid0.CachegroupID
+	eCG.SecondaryParentName = mid1.Cachegroup
+	eCG.SecondaryParentCachegroupID = mid1.CachegroupID
+	eCGType := tc.CacheGroupEdgeTypeName
+	eCG.Type = &eCGType
+
+	mCG0 := &tc.CacheGroupNullable{}
+	mCG0.Name = mid0.Cachegroup
+	mCG0.ID = mid0.CachegroupID
+	mCG0.ParentName = opl0.Cachegroup
+	mCG0.ParentCachegroupID = opl0.CachegroupID
+	mCG0.SecondaryParentName = opl1.Cachegroup
+	mCG0.SecondaryParentCachegroupID = opl1.CachegroupID
+	mCGType0 := tc.CacheGroupMidTypeName
+	mCG0.Type = &mCGType0
+
+	mCG1 := &tc.CacheGroupNullable{}
+	mCG1.Name = mid1.Cachegroup
+	mCG1.ID = mid1.CachegroupID
+	mCG1.ParentName = opl1.Cachegroup
+	mCG1.ParentCachegroupID = opl1.CachegroupID
+	mCG1.SecondaryParentName = opl0.Cachegroup
+	mCG1.SecondaryParentCachegroupID = opl0.CachegroupID
+	mCGType1 := tc.CacheGroupMidTypeName
+	mCG1.Type = &mCGType1
+
+	oplCG0 := &tc.CacheGroupNullable{}
+	oplCG0.Name = opl0.Cachegroup
+	oplCG0.ID = opl0.CachegroupID
+	oplCG0.ParentName = org0.Cachegroup
+	oplCG0.ParentCachegroupID = org0.CachegroupID
+	oplCG0.SecondaryParentName = org1.Cachegroup
+	oplCG0.SecondaryParentCachegroupID = org1.CachegroupID
+	oplCGType0 := tc.CacheGroupMidTypeName
+	oplCG0.Type = &oplCGType0
+
+	oplCG1 := &tc.CacheGroupNullable{}
+	oplCG1.Name = opl1.Cachegroup
+	oplCG1.ID = opl1.CachegroupID
+	oplCG1.ParentName = org1.Cachegroup
+	oplCG1.ParentCachegroupID = org1.CachegroupID
+	oplCG1.SecondaryParentName = org0.Cachegroup
+	oplCG1.SecondaryParentCachegroupID = org0.CachegroupID
+	oplCGType1 := tc.CacheGroupMidTypeName
+	oplCG1.Type = &oplCGType1
+
+	oCG0 := &tc.CacheGroupNullable{}
+	oCG0.Name = org0.Cachegroup
+	oCG0.ID = org0.CachegroupID
+	oCGType0 := tc.CacheGroupOriginTypeName
+	oCG0.Type = &oCGType0
+
+	oCG1 := &tc.CacheGroupNullable{}
+	oCG1.Name = org1.Cachegroup
+	oCG1.ID = org1.CachegroupID
+	oCGType1 := tc.CacheGroupOriginTypeName
+	oCG1.Type = &oCGType1
+
+	cgs := []tc.CacheGroupNullable{*eCG, *mCG0, *mCG1, *oplCG0, *oplCG1, *oCG0, *oCG1}
+
+	dss := []DeliveryServiceServer{
+		/*
+			{Server: *edge.ID, DeliveryService: *ds0.ID},
+			{Server: *mid0.ID, DeliveryService: *ds0.ID},
+			{Server: *mid1.ID, DeliveryService: *ds0.ID},
+			{Server: *opl0.ID, DeliveryService: *ds0.ID},
+			{Server: *opl1.ID, DeliveryService: *ds0.ID},
+			{Server: *org0.ID, DeliveryService: *ds0.ID},
+			{Server: *org1.ID, DeliveryService: *ds0.ID},
+
+			{Server: *edge.ID, DeliveryService: *ds1.ID},
+			{Server: *mid0.ID, DeliveryService: *ds1.ID},
+			{Server: *mid1.ID, DeliveryService: *ds1.ID},
+			{Server: *opl0.ID, DeliveryService: *ds1.ID},
+			{Server: *opl1.ID, DeliveryService: *ds1.ID},
+			{Server: *org0.ID, DeliveryService: *ds1.ID},
+			{Server: *org1.ID, DeliveryService: *ds1.ID},
+		*/
+	}
+	cdn := &tc.CDN{
+		DomainName: "cdndomain.example",
+		Name:       "my-cdn-name",
+	}
+
+	dsstrs := []string{
+		`dest_domain=ds0.example.net `,
+		`dest_domain=ds1.example.net `,
+	}
+
+	{ // test edge config
+		cfg, err := MakeParentDotConfig(dses, edge, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, hdr)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, hdr.HdrComment)
+
+		needs := []string{
+			` secondary_mode=2`,
+			` round_robin=consistent_hash`,
+			` go_direct=false`,
+			` parent_is_proxy=true`,
+			` parent_retry=both`,
+			` max_simple_retries=12`,
+			` max_unavailable_server_retries=22`,
+			` simple_server_retry_responses="401,402"`,
+			` unavailable_server_retry_responses="501,502"`,
+		}
+
+		for _, dsstr := range dsstrs {
+			cnt := strings.Count(txt, dsstr)
+			if 1 != cnt {
+				t.Errorf("Expected one entry for %s got %d\n%v", dsstr, cnt, txt)
+			} else {
+				lines := strings.Split(txt, "\n")
+				dsline := lineWhichContains(lines, dsstr)
+				missing := missingFrom(dsline, needs)
+				if 0 < len(missing) {
+					t.Errorf("Missing required string(s) from line: %v\n%v", missing, dsline)
+				}
+			}
+		}
+	}
+
+	{ // test mid config
+		cfg, err := MakeParentDotConfig(dses, mid0, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, hdr)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, hdr.HdrComment)
+
+		needs := []string{
+			` round_robin=consistent_hash`,
+			` go_direct=false`,
+			` parent_is_proxy=true`,
+			` parent_retry=both`,
+			` max_simple_retries=13`,
+			` max_unavailable_server_retries=23`,
+			` simple_server_retry_responses="401,403"`,
+			` unavailable_server_retry_responses="501,503"`,
+		}
+
+		for _, dsstr := range dsstrs {
+			cnt := strings.Count(txt, dsstr)
+			if 1 != cnt {
+				t.Errorf("Expected one entry for %s got %d\n%v", dsstr, cnt, txt)
+			} else {
+				lines := strings.Split(txt, "\n")
+				dsline := lineWhichContains(lines, dsstr)
+				missing := missingFrom(dsline, needs)
+				if 0 < len(missing) {
+					t.Errorf("Missing required string(s) from line: %v\n%v", missing, dsline)
+				}
+			}
+		}
+	}
+
+	{ // test opl config
+		cfg, err := MakeParentDotConfig(dses, opl0, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, hdr)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, hdr.HdrComment)
+
+		needs := []string{
+			` round_robin=true`,
+			` go_direct=true`,
+			` parent_is_proxy=false`,
+			` parent_retry=both`,
+			` max_simple_retries=14`,
+			` max_unavailable_server_retries=24`,
+			` simple_server_retry_responses="401,404"`,
+			` unavailable_server_retry_responses="501,504"`,
+		}
+
+		dsstr := `dest_domain=ds1.example.net `
+		cnt := strings.Count(txt, dsstr)
+		if 1 != cnt {
+			t.Errorf("Expected one entry for %s got %d\n%v", dsstr, cnt, txt)
+		} else {
+			lines := strings.Split(txt, "\n")
+			dsline := lineWhichContains(lines, dsstr)
+			missing := missingFrom(dsline, needs)
+			if 0 < len(missing) {
+				t.Errorf("Missing required string(s) from line: %v\n%v", missing, dsline)
+			}
+		}
+	}
+}
+
+// returns which elements in "needs" is missing from "line"
+func missingFrom(line string, needs []string) []string {
+	misses := []string{}
+	for _, need := range needs {
+		if !strings.Contains(line, need) {
+			misses = append(misses, need)
+		}
+	}
+	return misses
+}
+
+// returns first line which contains
+func lineWhichContains(lines []string, str string) (res string) {
+	for _, line := range lines {
+		if strings.Contains(line, str) {
+			res = line
+			break
+		}
+	}
+	return res
+}
+
 // warningsContains returns whether the given warnings has str as a substring of any warning.
 // Note this is different than lib/go-util.ContainsStr, which only returns if the array has the exact value as one of its values.
 func warningsContains(warnings []string, str string) bool {
diff --git a/lib/go-atscfg/snidotyaml_test.go b/lib/go-atscfg/snidotyaml_test.go
index c7d67eb6a9..33d125a5a7 100644
--- a/lib/go-atscfg/snidotyaml_test.go
+++ b/lib/go-atscfg/snidotyaml_test.go
@@ -59,7 +59,7 @@ func TestMakeSNIDotYAMLH2(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -170,7 +170,7 @@ func TestMakeSNIDotYAMLH2(t *testing.T) {
 				Profiles:   []byte(`["serverprofile"]`),
 			},
 			tc.Parameter{
-				Name:       ParentConfigParamAlgorithm,
+				Name:       ParentConfigRetryKeysDefault.Algorithm,
 				ConfigFile: "parent.config",
 				Value:      tc.AlgorithmConsistentHash,
 				Profiles:   []byte(`["serverprofile"]`),
@@ -223,7 +223,7 @@ func TestMakeSNIDotYAMLH2(t *testing.T) {
 				Profiles:   []byte(`["serverprofile"]`),
 			},
 			tc.Parameter{
-				Name:       ParentConfigParamAlgorithm,
+				Name:       ParentConfigRetryKeysDefault.Algorithm,
 				ConfigFile: "parent.config",
 				Value:      tc.AlgorithmConsistentHash,
 				Profiles:   []byte(`["serverprofile"]`),
@@ -270,7 +270,7 @@ func TestMakeSNIDotYAMLH2(t *testing.T) {
 				Profiles:   []byte(`["serverprofile"]`),
 			},
 			tc.Parameter{
-				Name:       ParentConfigParamAlgorithm,
+				Name:       ParentConfigRetryKeysDefault.Algorithm,
 				ConfigFile: "parent.config",
 				Value:      tc.AlgorithmConsistentHash,
 				Profiles:   []byte(`["serverprofile"]`),
diff --git a/lib/go-atscfg/sslservernamedotyaml_test.go b/lib/go-atscfg/sslservernamedotyaml_test.go
index 659733d1e0..1d1ab82f2a 100644
--- a/lib/go-atscfg/sslservernamedotyaml_test.go
+++ b/lib/go-atscfg/sslservernamedotyaml_test.go
@@ -54,7 +54,7 @@ func TestMakeSSLServerNameYAML(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -174,7 +174,7 @@ func TestMakeSSLServerNameYAMLParams(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -306,7 +306,7 @@ func TestMakeSSLServerNameYAMLParamInvalid(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -442,7 +442,7 @@ func TestMakeSSLServerNameYAMLDSTLSVersions(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
diff --git a/lib/go-atscfg/strategiesdotconfig.go b/lib/go-atscfg/strategiesdotconfig.go
index 0b2d46168c..b84b113bdf 100644
--- a/lib/go-atscfg/strategiesdotconfig.go
+++ b/lib/go-atscfg/strategiesdotconfig.go
@@ -155,10 +155,13 @@ func getStrategySecondaryMode(mode ParentAbstractionServiceParentSecondaryMode)
 }
 
 func getStrategyErrorCodes(codes []int) string {
-	str := ""
+	str := " ["
+	join := " "
 	for _, code := range codes {
-		str += "\n" + `        - ` + strconv.Itoa(code)
+		str += join + strconv.Itoa(code)
+		join = ", "
 	}
+	str += " ]"
 	return str
 }
 
diff --git a/lib/go-atscfg/strategiesdotconfig_test.go b/lib/go-atscfg/strategiesdotconfig_test.go
index 88d5b0bcb1..a354cac29e 100644
--- a/lib/go-atscfg/strategiesdotconfig_test.go
+++ b/lib/go-atscfg/strategiesdotconfig_test.go
@@ -55,7 +55,7 @@ func TestMakeStrategiesDotConfig(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -179,7 +179,7 @@ func TestMakeStrategiesTopologiesParams(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -191,33 +191,33 @@ func TestMakeStrategiesTopologiesParams(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
-			Value:      "consistent_hash",
+			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamParentRetry,
+			Name:       ParentConfigRetryKeysDefault.ParentRetry,
 			ConfigFile: "parent.config",
 			Value:      "both",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamUnavailableServerRetryResponses,
+			Name:       ParentConfigRetryKeysDefault.MaxSimpleRetries,
 			ConfigFile: "parent.config",
-			Value:      `"400,503"`,
+			Value:      "14",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxSimpleRetries,
+			Name:       ParentConfigRetryKeysDefault.MaxUnavailableRetries,
 			ConfigFile: "parent.config",
-			Value:      "14",
+			Value:      "9",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxUnavailableServerRetries,
+			Name:       ParentConfigRetryKeysDefault.UnavailableRetryResponses,
 			ConfigFile: "parent.config",
-			Value:      "9",
+			Value:      `"400,503"`,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 	}
@@ -314,8 +314,8 @@ func TestMakeStrategiesTopologiesParams(t *testing.T) {
 		"strategy:'strategy-ds1'",
 		"max_simple_retries:14",
 		"max_unavailable_retries:9",
-		"response_codes:\n-404",
-		"markdown_codes:\n-400\n-503",
+		"response_codes:[404]",
+		"markdown_codes:[400,503]",
 		"host__ds1__parent__myorigin0-dot-mydomain-dot-example-dot-net__80\nhost:myorigin0.mydomain.example.net",
 	}
 
@@ -353,7 +353,7 @@ func TestMakeStrategiesHTTPSOrigin(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -476,7 +476,7 @@ func TestMakeStrategiesPeeringRing(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -488,33 +488,33 @@ func TestMakeStrategiesPeeringRing(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
-			Value:      "consistent_hash",
+			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamParentRetry,
+			Name:       ParentConfigRetryKeysDefault.ParentRetry,
 			ConfigFile: "parent.config",
 			Value:      "both",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamUnavailableServerRetryResponses,
+			Name:       ParentConfigRetryKeysDefault.MaxSimpleRetries,
 			ConfigFile: "parent.config",
-			Value:      `"400,503"`,
+			Value:      "14",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxSimpleRetries,
+			Name:       ParentConfigRetryKeysDefault.MaxUnavailableRetries,
 			ConfigFile: "parent.config",
-			Value:      "14",
+			Value:      "9",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxUnavailableServerRetries,
+			Name:       ParentConfigRetryKeysDefault.UnavailableRetryResponses,
 			ConfigFile: "parent.config",
-			Value:      "9",
+			Value:      `"400,503"`,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 	}
@@ -629,8 +629,8 @@ func TestMakeStrategiesPeeringRing(t *testing.T) {
 			"strategy:'strategy-ds1'",
 			"max_simple_retries:14",
 			"max_unavailable_retries:9",
-			"response_codes:\n-404",
-			"markdown_codes:\n-400\n-503",
+			"response_codes:[404]",
+			"markdown_codes:[400,503]",
 			"host__ds1__parent__ds1-dot-example-dot-net__80\nhost:ds1.example.net",
 			"groups:\n-*peers_group\n-*group_parents_ds1", // peer ring group before parent group, param 'true'
 		}
@@ -666,8 +666,8 @@ func TestMakeStrategiesPeeringRing(t *testing.T) {
 			"strategy:'strategy-ds1'",
 			"max_simple_retries:14",
 			"max_unavailable_retries:9",
-			"response_codes:\n-404",
-			"markdown_codes:\n-400\n-503",
+			"response_codes:[404]",
+			"markdown_codes:[400,503]",
 			"host__ds1__parent__ds1-dot-example-dot-net__80\nhost:ds1.example.net",
 			"groups:\n-*group_parents_ds1\nfailover:", // no peer ring group, param is not 'true'
 		}
@@ -694,8 +694,8 @@ func TestMakeStrategiesPeeringRing(t *testing.T) {
 			"strategy:'strategy-ds1'",
 			"max_simple_retries:14",
 			"max_unavailable_retries:9",
-			"response_codes:\n-404",
-			"markdown_codes:\n-400\n-503",
+			"response_codes:[404]",
+			"markdown_codes:[400,503]",
 			"host__ds1__parent__ds1-dot-example-dot-net__80\nhost:ds1.example.net",
 			"groups:\n-*group_parents_ds1\nfailover:", // no peer ring group, no parameter
 		}
@@ -732,7 +732,7 @@ func TestMakeStrategiesPeeringRingMSO(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -744,33 +744,33 @@ func TestMakeStrategiesPeeringRingMSO(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
-			Value:      "consistent_hash",
+			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamParentRetry,
+			Name:       ParentConfigRetryKeysDefault.ParentRetry,
 			ConfigFile: "parent.config",
 			Value:      "both",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamUnavailableServerRetryResponses,
+			Name:       ParentConfigRetryKeysDefault.MaxSimpleRetries,
 			ConfigFile: "parent.config",
-			Value:      `"400,503"`,
+			Value:      "14",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxSimpleRetries,
+			Name:       ParentConfigRetryKeysDefault.MaxUnavailableRetries,
 			ConfigFile: "parent.config",
-			Value:      "14",
+			Value:      "9",
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamMaxUnavailableServerRetries,
+			Name:       ParentConfigRetryKeysDefault.UnavailableRetryResponses,
 			ConfigFile: "parent.config",
-			Value:      "9",
+			Value:      `"400,503"`,
 			Profiles:   []byte(`["ds1Profile"]`),
 		},
 	}
@@ -885,8 +885,8 @@ func TestMakeStrategiesPeeringRingMSO(t *testing.T) {
 			"strategy:'strategy-ds1'",
 			"max_simple_retries:14",
 			"max_unavailable_retries:9",
-			"response_codes:\n-404",
-			"markdown_codes:\n-400\n-503",
+			"response_codes:[404]",
+			"markdown_codes:[400,503]",
 			"host__ds1__parent__myorigin0-dot-mydomain-dot-example-dot-net__80\nhost:myorigin0.mydomain.example.net",
 			"groups:\n-*peers_group\n-*group_parents_ds1", // peer ring group before parent group, param 'true'
 		}
@@ -922,8 +922,8 @@ func TestMakeStrategiesPeeringRingMSO(t *testing.T) {
 			"strategy:'strategy-ds1'",
 			"max_simple_retries:14",
 			"max_unavailable_retries:9",
-			"response_codes:\n-404",
-			"markdown_codes:\n-400\n-503",
+			"response_codes:[404]",
+			"markdown_codes:[400,503]",
 			"host__ds1__parent__myorigin0-dot-mydomain-dot-example-dot-net__80\nhost:myorigin0.mydomain.example.net",
 			"groups:\n-*group_parents_ds1\nfailover:", // no peer ring group, param is not 'true'
 		}
@@ -950,8 +950,8 @@ func TestMakeStrategiesPeeringRingMSO(t *testing.T) {
 			"strategy:'strategy-ds1'",
 			"max_simple_retries:14",
 			"max_unavailable_retries:9",
-			"response_codes:\n-404",
-			"markdown_codes:\n-400\n-503",
+			"response_codes:[404]",
+			"markdown_codes:[400,503]",
 			"host__ds1__parent__myorigin0-dot-mydomain-dot-example-dot-net__80\nhost:myorigin0.mydomain.example.net",
 			"groups:\n-*group_parents_ds1\nfailover:", // no peer ring group, no parameter
 		}
@@ -993,7 +993,7 @@ func TestMakeStrategiesPeeringRingNonTopology(t *testing.T) {
 			Profiles:   []byte(`["serverprofile"]`),
 		},
 		tc.Parameter{
-			Name:       ParentConfigParamAlgorithm,
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
 			ConfigFile: "parent.config",
 			Value:      tc.AlgorithmConsistentHash,
 			Profiles:   []byte(`["serverprofile"]`),
@@ -1187,3 +1187,664 @@ func TestMakeStrategiesPeeringRingNonTopology(t *testing.T) {
 		}
 	})
 }
+
+func TestMakeStrategiesDotYAMLFirstLastNoTopoParams(t *testing.T) {
+	opt := &StrategiesYAMLOpts{VerboseComments: false, HdrComment: "myHeaderComment"}
+
+	// Non Toplogy
+	ds0 := makeParentDS()
+	ds0.ID = util.IntPtr(42)
+	ds0Type := tc.DSTypeDNS
+	ds0.Type = &ds0Type
+	ds0.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreDrop))
+	ds0.OrgServerFQDN = util.StrPtr("http://ds0.example.net")
+	ds0.ProfileID = util.IntPtr(310)
+	ds0.ProfileName = util.StrPtr("ds0Profile")
+
+	// Non Toplogy, MSO
+	ds1 := makeParentDS()
+	ds1.ID = util.IntPtr(43)
+	ds1Type := tc.DSTypeDNS
+	ds1.Type = &ds1Type
+	ds1.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreDrop))
+	ds1.OrgServerFQDN = util.StrPtr("http://ds1.example.net")
+	ds1.ProfileID = util.IntPtr(310)
+	ds1.ProfileName = util.StrPtr("ds0Profile")
+	ds1.MultiSiteOrigin = util.BoolPtr(true)
+
+	dsesall := []DeliveryService{*ds0, *ds1}
+
+	parentConfigParams := []tc.Parameter{
+		{
+			Name:       ParentConfigParamQStringHandling,
+			ConfigFile: "parent.config",
+			Value:      "myQStringHandlingParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		{
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
+			ConfigFile: "parent.config",
+			Value:      tc.AlgorithmConsistentHash,
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		{
+			Name:       ParentConfigParamQString,
+			ConfigFile: "parent.config",
+			Value:      "myQstringParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+	}
+
+	// Create set of DS params
+	params := map[string]string{
+		ParentConfigRetryKeysDefault.Algorithm: "strict",
+		ParentConfigRetryKeysMSO.Algorithm:     "strict",
+		ParentConfigRetryKeysFirst.Algorithm:   "true",
+		ParentConfigRetryKeysInner.Algorithm:   "latched",
+		ParentConfigRetryKeysLast.Algorithm:    "true",
+
+		ParentConfigRetryKeysDefault.SecondaryMode: "exhaust",
+		ParentConfigRetryKeysMSO.SecondaryMode:     "exhaust",
+		ParentConfigRetryKeysFirst.SecondaryMode:   "alternate",
+		ParentConfigRetryKeysInner.SecondaryMode:   "peering",
+		ParentConfigRetryKeysLast.SecondaryMode:    "alternate",
+
+		ParentConfigRetryKeysMSO.ParentRetry:   "unavailable_server_retry",
+		ParentConfigRetryKeysFirst.ParentRetry: "both",
+		ParentConfigRetryKeysInner.ParentRetry: "both",
+		ParentConfigRetryKeysLast.ParentRetry:  "both",
+
+		ParentConfigRetryKeysDefault.MaxSimpleRetries: "11",
+		ParentConfigRetryKeysMSO.MaxSimpleRetries:     "11",
+		ParentConfigRetryKeysFirst.MaxSimpleRetries:   "12",
+		ParentConfigRetryKeysInner.MaxSimpleRetries:   "13",
+		ParentConfigRetryKeysLast.MaxSimpleRetries:    "14",
+
+		ParentConfigRetryKeysDefault.SimpleRetryResponses: `"401"`,
+		ParentConfigRetryKeysMSO.SimpleRetryResponses:     `"401"`,
+		ParentConfigRetryKeysFirst.SimpleRetryResponses:   `"401,402"`,
+		ParentConfigRetryKeysInner.SimpleRetryResponses:   `"401,403"`,
+		ParentConfigRetryKeysLast.SimpleRetryResponses:    `"401,404"`,
+
+		ParentConfigRetryKeysDefault.MaxUnavailableRetries: "21",
+		ParentConfigRetryKeysMSO.MaxUnavailableRetries:     "21",
+		ParentConfigRetryKeysFirst.MaxUnavailableRetries:   "22",
+		ParentConfigRetryKeysInner.MaxUnavailableRetries:   "23",
+		ParentConfigRetryKeysLast.MaxUnavailableRetries:    "24",
+
+		ParentConfigRetryKeysDefault.UnavailableRetryResponses: `"501"`,
+		ParentConfigRetryKeysMSO.UnavailableRetryResponses:     `"501"`,
+		ParentConfigRetryKeysFirst.UnavailableRetryResponses:   `"501,502"`,
+		ParentConfigRetryKeysInner.UnavailableRetryResponses:   `"501,503"`,
+		ParentConfigRetryKeysLast.UnavailableRetryResponses:    `"501,504"`,
+	}
+
+	// Assign them to the profile
+	for key, val := range params {
+		tcparam := tc.Parameter{
+			Name:       key,
+			ConfigFile: "parent.config",
+			Value:      val,
+			Profiles:   []byte(`["ds0Profile"]`),
+		}
+		parentConfigParams = append(parentConfigParams, tcparam)
+	}
+
+	serverParams := []tc.Parameter{
+		{
+			Name:       "trafficserver",
+			ConfigFile: "package",
+			Value:      "9",
+			Profiles:   []byte(`["global"]`),
+		},
+	}
+
+	edge := makeTestParentServer()
+	edge.Cachegroup = util.StrPtr("edgeCG")
+	edge.CachegroupID = util.IntPtr(400)
+
+	mid0 := makeTestParentServer()
+	mid0.Cachegroup = util.StrPtr("midCG0")
+	mid0.CachegroupID = util.IntPtr(500)
+	mid0.HostName = util.StrPtr("mymid0")
+	mid0.ID = util.IntPtr(45)
+	setIP(mid0, "192.168.2.2")
+	mid0.Type = tc.CacheGroupMidTypeName
+	mid0.TypeID = util.IntPtr(990)
+
+	mid1 := makeTestParentServer()
+	mid1.Cachegroup = util.StrPtr("midCG1")
+	mid1.CachegroupID = util.IntPtr(501)
+	mid1.HostName = util.StrPtr("mymid1")
+	mid1.ID = util.IntPtr(46)
+	setIP(mid1, "192.168.2.3")
+	mid1.Type = tc.CacheGroupMidTypeName
+	mid1.TypeID = util.IntPtr(990)
+
+	org0 := makeTestParentServer()
+	org0.Cachegroup = util.StrPtr("orgCG0")
+	org0.CachegroupID = util.IntPtr(502)
+	org0.HostName = util.StrPtr("myorg0")
+	org0.ID = util.IntPtr(48)
+	setIP(org0, "192.168.2.4")
+	org0.Type = tc.OriginTypeName
+	org0.TypeID = util.IntPtr(991)
+
+	org1 := makeTestParentServer()
+	org1.Cachegroup = util.StrPtr("orgCG1")
+	org1.CachegroupID = util.IntPtr(503)
+	org1.HostName = util.StrPtr("myorg1")
+	org1.ID = util.IntPtr(49)
+	setIP(org1, "192.168.2.5")
+	org1.Type = tc.OriginTypeName
+	org1.TypeID = util.IntPtr(991)
+
+	servers := []Server{*edge, *mid0, *mid1, *org0, *org1}
+
+	topologies := []tc.Topology{
+		{
+			Name: "t0",
+			Nodes: []tc.TopologyNode{
+				{
+					Cachegroup: "edgeCG",
+					Parents:    []int{1, 2},
+				},
+				{
+					Cachegroup: "midCG0",
+					Parents:    []int{3, 4},
+				},
+				{
+					Cachegroup: "midCG1",
+					Parents:    []int{3, 4},
+				},
+				{
+					Cachegroup: "orgCG0",
+				},
+				{
+					Cachegroup: "orgCG1",
+				},
+			},
+		},
+	}
+
+	serverCapabilities := map[int]map[ServerCapability]struct{}{}
+	dsRequiredCapabilities := map[int]map[ServerCapability]struct{}{}
+
+	eCG := &tc.CacheGroupNullable{}
+	eCG.Name = edge.Cachegroup
+	eCG.ID = edge.CachegroupID
+	eCG.ParentName = mid0.Cachegroup
+	eCG.ParentCachegroupID = mid0.CachegroupID
+	eCG.SecondaryParentName = mid1.Cachegroup
+	eCG.SecondaryParentCachegroupID = mid1.CachegroupID
+	eCGType := tc.CacheGroupEdgeTypeName
+	eCG.Type = &eCGType
+
+	mCG0 := &tc.CacheGroupNullable{}
+	mCG0.Name = mid0.Cachegroup
+	mCG0.ID = mid0.CachegroupID
+	mCG0.ParentName = org0.Cachegroup
+	mCG0.ParentCachegroupID = org0.CachegroupID
+	mCG0.SecondaryParentName = org1.Cachegroup
+	mCG0.SecondaryParentCachegroupID = org1.CachegroupID
+	mCGType0 := tc.CacheGroupMidTypeName
+	mCG0.Type = &mCGType0
+
+	mCG1 := &tc.CacheGroupNullable{}
+	mCG1.Name = mid1.Cachegroup
+	mCG1.ID = mid1.CachegroupID
+	mCG1.ParentName = org1.Cachegroup
+	mCG1.ParentCachegroupID = org1.CachegroupID
+	mCG1.SecondaryParentName = org0.Cachegroup
+	mCG1.SecondaryParentCachegroupID = org0.CachegroupID
+	mCGType1 := tc.CacheGroupMidTypeName
+	mCG1.Type = &mCGType1
+
+	oCG0 := &tc.CacheGroupNullable{}
+	oCG0.Name = org0.Cachegroup
+	oCG0.ID = org0.CachegroupID
+	oCGType0 := tc.CacheGroupOriginTypeName
+	oCG0.Type = &oCGType0
+
+	oCG1 := &tc.CacheGroupNullable{}
+	oCG1.Name = org1.Cachegroup
+	oCG1.ID = org1.CachegroupID
+	oCGType1 := tc.CacheGroupOriginTypeName
+	oCG1.Type = &oCGType1
+
+	cgs := []tc.CacheGroupNullable{*eCG, *mCG0, *mCG1, *oCG0, *oCG1}
+
+	dss := []DeliveryServiceServer{
+		{Server: *edge.ID, DeliveryService: *ds0.ID},
+		{Server: *mid0.ID, DeliveryService: *ds0.ID},
+		{Server: *mid1.ID, DeliveryService: *ds0.ID},
+		{Server: *org0.ID, DeliveryService: *ds0.ID},
+		{Server: *org1.ID, DeliveryService: *ds0.ID},
+
+		{Server: *edge.ID, DeliveryService: *ds1.ID},
+		{Server: *mid0.ID, DeliveryService: *ds1.ID},
+		{Server: *mid1.ID, DeliveryService: *ds1.ID},
+		{Server: *org0.ID, DeliveryService: *ds1.ID},
+		{Server: *org1.ID, DeliveryService: *ds1.ID},
+	}
+	cdn := &tc.CDN{
+		DomainName: "cdndomain.example",
+		Name:       "my-cdn-name",
+	}
+
+	// edge config
+	/*
+		for _, ds := range dsesall {
+			dses := []DeliveryService{ds}
+			cfg, err := MakeStrategiesDotYAML(dses, edge, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+			if err != nil {
+				t.Fatal(err)
+			}
+			txt := cfg.Text
+
+			testComment(t, txt, opt.HdrComment)
+
+			needs := []string{
+				` policy: consistent_hash`,
+				` go_direct: false`,
+				` max_simple_retries: 12`,
+				` max_unavailable_retries: 22`,
+				` response_codes: [ 401, 402 ]`,
+				` markdown_codes: [ 501, 502 ]`,
+			}
+
+			missing := missingFrom(txt, needs)
+			if 0 < len(missing) {
+				t.Errorf("Missing required string(s) from line: %v\n%v", missing, txt)
+			}
+		}
+	*/
+
+	// test mid config, MS only
+	{
+		ds := dsesall[1]
+		dses := []DeliveryService{ds}
+		cfg, err := MakeStrategiesDotYAML(dses, mid0, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, opt.HdrComment)
+
+		needs := []string{
+			` policy: rr_ip`,
+			` go_direct: true`,
+			` max_simple_retries: 14`,
+			` max_unavailable_retries: 24`,
+			` response_codes: [ 401, 404 ]`,
+			` markdown_codes: [ 501, 504 ]`,
+		}
+
+		missing := missingFrom(txt, needs)
+		if 0 < len(missing) {
+			t.Errorf("Missing required string(s) from ds/line: %s/%v\n%v", *ds.XMLID, missing, txt)
+		}
+	}
+}
+
+func TestMakeStrategiesDotYAMLFirstInnerLastParams(t *testing.T) {
+	opt := &StrategiesYAMLOpts{VerboseComments: false, HdrComment: "myHeaderComment"}
+
+	// Toplogy ds, MSO
+	ds0 := makeParentDS()
+	ds0Type := tc.DSTypeHTTP
+	ds0.Type = &ds0Type
+	ds0.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreUseInCacheKeyAndPassUp))
+	ds0.OrgServerFQDN = util.StrPtr("http://ds0.example.net")
+	ds0.ProfileID = util.IntPtr(311)
+	ds0.ProfileName = util.StrPtr("ds0Profile")
+	ds0.MultiSiteOrigin = util.BoolPtr(true)
+	ds0.Topology = util.StrPtr("t0")
+
+	// Toplogy ds, non MSO
+	ds1 := makeParentDS()
+	ds1.ID = util.IntPtr(43)
+	ds1Type := tc.DSTypeDNS
+	ds1.Type = &ds1Type
+	ds1.QStringIgnore = util.IntPtr(int(tc.QStringIgnoreDrop))
+	ds1.OrgServerFQDN = util.StrPtr("http://ds1.example.net")
+	ds1.ProfileID = util.IntPtr(311)
+	ds1.ProfileName = util.StrPtr("ds0Profile")
+	ds1.Topology = util.StrPtr("t0")
+
+	dsesall := []DeliveryService{*ds0, *ds1}
+
+	parentConfigParams := []tc.Parameter{
+		{
+			Name:       ParentConfigParamQStringHandling,
+			ConfigFile: "parent.config",
+			Value:      "myQStringHandlingParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		{
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
+			ConfigFile: "parent.config",
+			Value:      tc.AlgorithmConsistentHash,
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		{
+			Name:       ParentConfigParamQString,
+			ConfigFile: "parent.config",
+			Value:      "myQstringParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+	}
+
+	// Create set of DS params
+	params := map[string]string{
+		ParentConfigRetryKeysDefault.Algorithm: "strict",
+		ParentConfigRetryKeysMSO.Algorithm:     "strict",
+		ParentConfigRetryKeysFirst.Algorithm:   "true",
+		ParentConfigRetryKeysInner.Algorithm:   "latched",
+		ParentConfigRetryKeysLast.Algorithm:    "true",
+
+		ParentConfigRetryKeysDefault.SecondaryMode: "exhaust",
+		ParentConfigRetryKeysMSO.SecondaryMode:     "exhaust",
+		ParentConfigRetryKeysFirst.SecondaryMode:   "alternate",
+		ParentConfigRetryKeysInner.SecondaryMode:   "peering",
+		ParentConfigRetryKeysLast.SecondaryMode:    "alternate",
+
+		ParentConfigRetryKeysMSO.ParentRetry:   "unavailable_server_retry",
+		ParentConfigRetryKeysFirst.ParentRetry: "both",
+		ParentConfigRetryKeysInner.ParentRetry: "both",
+		ParentConfigRetryKeysLast.ParentRetry:  "both",
+
+		ParentConfigRetryKeysDefault.MaxSimpleRetries: "11",
+		ParentConfigRetryKeysMSO.MaxSimpleRetries:     "11",
+		ParentConfigRetryKeysFirst.MaxSimpleRetries:   "12",
+		ParentConfigRetryKeysInner.MaxSimpleRetries:   "13",
+		ParentConfigRetryKeysLast.MaxSimpleRetries:    "14",
+
+		ParentConfigRetryKeysDefault.SimpleRetryResponses: `"401"`,
+		ParentConfigRetryKeysMSO.SimpleRetryResponses:     `"401"`,
+		ParentConfigRetryKeysFirst.SimpleRetryResponses:   `"401,402"`,
+		ParentConfigRetryKeysInner.SimpleRetryResponses:   `"401,403"`,
+		ParentConfigRetryKeysLast.SimpleRetryResponses:    `"401,404"`,
+
+		ParentConfigRetryKeysDefault.MaxUnavailableRetries: "21",
+		ParentConfigRetryKeysMSO.MaxUnavailableRetries:     "21",
+		ParentConfigRetryKeysFirst.MaxUnavailableRetries:   "22",
+		ParentConfigRetryKeysInner.MaxUnavailableRetries:   "23",
+		ParentConfigRetryKeysLast.MaxUnavailableRetries:    "24",
+
+		ParentConfigRetryKeysDefault.UnavailableRetryResponses: `"501"`,
+		ParentConfigRetryKeysMSO.UnavailableRetryResponses:     `"501"`,
+		ParentConfigRetryKeysFirst.UnavailableRetryResponses:   `"501,502"`,
+		ParentConfigRetryKeysInner.UnavailableRetryResponses:   `"501,503"`,
+		ParentConfigRetryKeysLast.UnavailableRetryResponses:    `"501,504"`,
+	}
+
+	// Assign them to the profile
+	for key, val := range params {
+		tcparam := tc.Parameter{
+			Name:       key,
+			ConfigFile: "parent.config",
+			Value:      val,
+			Profiles:   []byte(`["ds0Profile"]`),
+		}
+		parentConfigParams = append(parentConfigParams, tcparam)
+	}
+
+	serverParams := []tc.Parameter{
+		{
+			Name:       "trafficserver",
+			ConfigFile: "package",
+			Value:      "9",
+			Profiles:   []byte(`["global"]`),
+		},
+	}
+
+	edge := makeTestParentServer()
+	edge.Cachegroup = util.StrPtr("edgeCG")
+	edge.CachegroupID = util.IntPtr(400)
+
+	mid0 := makeTestParentServer()
+	mid0.Cachegroup = util.StrPtr("midCG0")
+	mid0.CachegroupID = util.IntPtr(500)
+	mid0.HostName = util.StrPtr("mymid0")
+	mid0.ID = util.IntPtr(45)
+	setIP(mid0, "192.168.2.2")
+
+	mid1 := makeTestParentServer()
+	mid1.Cachegroup = util.StrPtr("midCG1")
+	mid1.CachegroupID = util.IntPtr(501)
+	mid1.HostName = util.StrPtr("mymid1")
+	mid1.ID = util.IntPtr(46)
+	setIP(mid1, "192.168.2.3")
+
+	opl0 := makeTestParentServer()
+	opl0.Cachegroup = util.StrPtr("oplCG0")
+	opl0.CachegroupID = util.IntPtr(502)
+	opl0.HostName = util.StrPtr("myopl0")
+	opl0.ID = util.IntPtr(46)
+	setIP(opl0, "192.168.2.4")
+
+	opl1 := makeTestParentServer()
+	opl1.Cachegroup = util.StrPtr("oplCG1")
+	opl1.CachegroupID = util.IntPtr(503)
+	opl1.HostName = util.StrPtr("myopl1")
+	opl1.ID = util.IntPtr(47)
+	setIP(opl1, "192.168.2.5")
+
+	org0 := makeTestParentServer()
+	org0.Cachegroup = util.StrPtr("orgCG0")
+	org0.CachegroupID = util.IntPtr(504)
+	org0.HostName = util.StrPtr("myorg0")
+	org0.ID = util.IntPtr(48)
+	setIP(org0, "192.168.2.6")
+	org0.Type = tc.OriginTypeName
+	org0.TypeID = util.IntPtr(991)
+
+	org1 := makeTestParentServer()
+	org1.Cachegroup = util.StrPtr("orgCG1")
+	org1.CachegroupID = util.IntPtr(505)
+	org1.HostName = util.StrPtr("myorg1")
+	org1.ID = util.IntPtr(49)
+	setIP(org1, "192.168.2.7")
+	org1.Type = tc.OriginTypeName
+	org1.TypeID = util.IntPtr(991)
+
+	servers := []Server{*edge, *mid0, *mid1, *opl0, *opl1, *org0, *org1}
+
+	topologies := []tc.Topology{
+		{
+			Name: "t0",
+			Nodes: []tc.TopologyNode{
+				{
+					Cachegroup: "edgeCG",
+					Parents:    []int{1, 2},
+				},
+				{
+					Cachegroup: "midCG0",
+					Parents:    []int{3, 4},
+				},
+				{
+					Cachegroup: "midCG1",
+					Parents:    []int{3, 4},
+				},
+				{
+					Cachegroup: "oplCG0",
+					Parents:    []int{5, 6},
+				},
+				{
+					Cachegroup: "oplCG1",
+					Parents:    []int{5, 6},
+				},
+				{
+					Cachegroup: "orgCG0",
+				},
+				{
+					Cachegroup: "orgCG1",
+				},
+			},
+		},
+	}
+
+	serverCapabilities := map[int]map[ServerCapability]struct{}{}
+	dsRequiredCapabilities := map[int]map[ServerCapability]struct{}{}
+
+	eCG := &tc.CacheGroupNullable{}
+	eCG.Name = edge.Cachegroup
+	eCG.ID = edge.CachegroupID
+	eCG.ParentName = mid0.Cachegroup
+	eCG.ParentCachegroupID = mid0.CachegroupID
+	eCG.SecondaryParentName = mid1.Cachegroup
+	eCG.SecondaryParentCachegroupID = mid1.CachegroupID
+	eCGType := tc.CacheGroupEdgeTypeName
+	eCG.Type = &eCGType
+
+	mCG0 := &tc.CacheGroupNullable{}
+	mCG0.Name = mid0.Cachegroup
+	mCG0.ID = mid0.CachegroupID
+	mCG0.ParentName = opl0.Cachegroup
+	mCG0.ParentCachegroupID = opl0.CachegroupID
+	mCG0.SecondaryParentName = opl1.Cachegroup
+	mCG0.SecondaryParentCachegroupID = opl1.CachegroupID
+	mCGType0 := tc.CacheGroupMidTypeName
+	mCG0.Type = &mCGType0
+
+	mCG1 := &tc.CacheGroupNullable{}
+	mCG1.Name = mid1.Cachegroup
+	mCG1.ID = mid1.CachegroupID
+	mCG1.ParentName = opl1.Cachegroup
+	mCG1.ParentCachegroupID = opl1.CachegroupID
+	mCG1.SecondaryParentName = opl0.Cachegroup
+	mCG1.SecondaryParentCachegroupID = opl0.CachegroupID
+	mCGType1 := tc.CacheGroupMidTypeName
+	mCG1.Type = &mCGType1
+
+	oplCG0 := &tc.CacheGroupNullable{}
+	oplCG0.Name = opl0.Cachegroup
+	oplCG0.ID = opl0.CachegroupID
+	oplCG0.ParentName = org0.Cachegroup
+	oplCG0.ParentCachegroupID = org0.CachegroupID
+	oplCG0.SecondaryParentName = org1.Cachegroup
+	oplCG0.SecondaryParentCachegroupID = org1.CachegroupID
+	oplCGType0 := tc.CacheGroupMidTypeName
+	oplCG0.Type = &oplCGType0
+
+	oplCG1 := &tc.CacheGroupNullable{}
+	oplCG1.Name = opl1.Cachegroup
+	oplCG1.ID = opl1.CachegroupID
+	oplCG1.ParentName = org1.Cachegroup
+	oplCG1.ParentCachegroupID = org1.CachegroupID
+	oplCG1.SecondaryParentName = org0.Cachegroup
+	oplCG1.SecondaryParentCachegroupID = org0.CachegroupID
+	oplCGType1 := tc.CacheGroupMidTypeName
+	oplCG1.Type = &oplCGType1
+
+	oCG0 := &tc.CacheGroupNullable{}
+	oCG0.Name = org0.Cachegroup
+	oCG0.ID = org0.CachegroupID
+	oCGType0 := tc.CacheGroupOriginTypeName
+	oCG0.Type = &oCGType0
+
+	oCG1 := &tc.CacheGroupNullable{}
+	oCG1.Name = org1.Cachegroup
+	oCG1.ID = org1.CachegroupID
+	oCGType1 := tc.CacheGroupOriginTypeName
+	oCG1.Type = &oCGType1
+
+	cgs := []tc.CacheGroupNullable{*eCG, *mCG0, *mCG1, *oplCG0, *oplCG1, *oCG0, *oCG1}
+
+	dss := []DeliveryServiceServer{
+		{Server: *org0.ID, DeliveryService: *ds0.ID},
+		{Server: *org1.ID, DeliveryService: *ds0.ID},
+
+		{Server: *edge.ID, DeliveryService: *ds1.ID},
+		{Server: *mid0.ID, DeliveryService: *ds1.ID},
+		{Server: *mid1.ID, DeliveryService: *ds1.ID},
+		{Server: *opl0.ID, DeliveryService: *ds1.ID},
+		{Server: *opl1.ID, DeliveryService: *ds1.ID},
+		{Server: *org0.ID, DeliveryService: *ds1.ID},
+		{Server: *org1.ID, DeliveryService: *ds1.ID},
+	}
+	cdn := &tc.CDN{
+		DomainName: "cdndomain.example",
+		Name:       "my-cdn-name",
+	}
+
+	// edge config
+	for _, ds := range dsesall {
+		dses := []DeliveryService{ds}
+		cfg, err := MakeStrategiesDotYAML(dses, edge, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, opt.HdrComment)
+
+		needs := []string{
+			` policy: consistent_hash`,
+			` go_direct: false`,
+			` max_simple_retries: 12`,
+			` max_unavailable_retries: 22`,
+			` response_codes: [ 401, 402 ]`,
+			` markdown_codes: [ 501, 502 ]`,
+		}
+
+		missing := missingFrom(txt, needs)
+		if 0 < len(missing) {
+			t.Errorf("Missing required string(s) from line: %v\n%v", missing, txt)
+		}
+	}
+
+	// test mid config
+	for _, ds := range dsesall {
+		dses := []DeliveryService{ds}
+		cfg, err := MakeStrategiesDotYAML(dses, mid0, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, opt.HdrComment)
+
+		needs := []string{
+			` policy: consistent_hash`,
+			` go_direct: false`,
+			` max_simple_retries: 13`,
+			` max_unavailable_retries: 23`,
+			` response_codes: [ 401, 403 ]`,
+			` markdown_codes: [ 501, 503 ]`,
+		}
+
+		missing := missingFrom(txt, needs)
+		if 0 < len(missing) {
+			t.Errorf("Missing required string(s) from ds/line: %s/%v\n%v", *ds.XMLID, missing, txt)
+		}
+	}
+
+	// test opl config
+	for _, ds := range dsesall {
+		dses := []DeliveryService{ds}
+		cfg, err := MakeStrategiesDotYAML(dses, opl0, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, opt.HdrComment)
+
+		needs := []string{
+			` policy: rr_ip`,
+			` go_direct: true`,
+			` max_simple_retries: 14`,
+			` max_unavailable_retries: 24`,
+			` response_codes: [ 401, 404 ]`,
+			` markdown_codes: [ 501, 504 ]`,
+		}
+
+		missing := missingFrom(txt, needs)
+		if 0 < len(missing) {
+			t.Errorf("Missing required string(s) from line: %v\n%v", missing, txt)
+		}
+	}
+}