You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by oc...@apache.org on 2022/08/26 18:13:15 UTC

[trafficcontrol] branch master updated: Add t3c flag for local ATS version for config gen (#7032)

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

ocket8888 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 3b21783291 Add t3c flag for local ATS version for config gen (#7032)
3b21783291 is described below

commit 3b217832915e4f2e3c2e65cf6e7807fdd40842b5
Author: Robert O Butts <ro...@users.noreply.github.com>
AuthorDate: Fri Aug 26 12:13:09 2022 -0600

    Add t3c flag for local ATS version for config gen (#7032)
---
 CHANGELOG.md                                  |   1 +
 cache-config/t3c-apply/README.md              | 202 +++++++++++----------
 cache-config/t3c-apply/config/config.go       |  32 ++++
 cache-config/t3c-apply/torequest/cmd.go       |   3 +
 cache-config/t3c-generate/README.md           |   7 +
 cache-config/t3c-generate/cfgfile/all.go      |   2 +-
 cache-config/t3c-generate/cfgfile/wrappers.go |  33 ++--
 cache-config/t3c-generate/config/config.go    |  14 ++
 lib/go-atscfg/atscfg.go                       |  36 +++-
 lib/go-atscfg/atscfg_test.go                  |   6 +-
 lib/go-atscfg/headerrewritedotconfig.go       |  15 +-
 lib/go-atscfg/loggingdotyaml.go               |  22 ++-
 lib/go-atscfg/meta.go                         |  20 ++-
 lib/go-atscfg/parentabstraction.go            |   6 +-
 lib/go-atscfg/parentdotconfig.go              |  67 +++----
 lib/go-atscfg/parentdotconfig_test.go         | 245 ++++++++++++++++++++++++++
 lib/go-atscfg/remapdotconfig.go               |  20 ++-
 lib/go-atscfg/strategiesdotconfig.go          |  28 ++-
 18 files changed, 579 insertions(+), 180 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a546bb01f..0a5579a7f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 ## [unreleased]
 ### Added
 - [#6033](https://github.com/apache/trafficcontrol/issues/6033) [Traffic Ops, Traffic Portal] Added ability to assign multiple server capabilities to a server.
+- [#7032](https://github.com/apache/trafficcontrol/issues/7032) Add t3c-apply flag to use local ATS version for config generation rather than Server package Parameter, to allow managing the ATS OS package via external tools. See 'man t3c-apply' and 'man t3c-generate' for details.
 - [Traffic Monitor] Added logging for `ipv4Availability` and `ipv6Availability` in TM.
 
 ### Fixed
diff --git a/cache-config/t3c-apply/README.md b/cache-config/t3c-apply/README.md
index deddfd64fd..8f16e760db 100644
--- a/cache-config/t3c-apply/README.md
+++ b/cache-config/t3c-apply/README.md
@@ -54,189 +54,207 @@ Typical usage is to install t3c on the cache machine, and then run it periodical
 
 -2, -\-default-client-enable-h2
 
-                    Whether to enable HTTP/2 on Delivery Services by default, if
-                    they have no explicit Parameter. This is irrelevant if ATS
-                    records.config is not serving H2. If omitted, H2 is
-                    disabled.
+    Whether to enable HTTP/2 on Delivery Services by default, if
+    they have no explicit Parameter. This is irrelevant if ATS
+    records.config is not serving H2. If omitted, H2 is
+    disabled.
 
 -a, -\-service-action=value
 
-                    action to perform on Traffic Server and other system
-                    services. Only reloads if necessary, but always restarts.
-                    Default is 'reload'
+    The action to perform on Traffic Server and other system
+    services. Only reloads if necessary, but always restarts.
+    Default is 'reload'
 
 -A, -\-update-ipallow
 
-                    Whether ipallow file will be updated if necessary. This
-                    exists because ATS had a bug where reloading after changing
-                    ipallow would block everything. Default is false.
+    Whether ipallow file will be updated if necessary. This
+    exists because ATS had a bug where reloading after changing
+    ipallow would block everything. Default is false.
+
 -b, -\-dns-local-bind
 
-                    [true | false] whether to use the server's Service Addresses
-                    to set the ATS DNS local bind address
+    [true | false] whether to use the server's Service Addresses
+    to set the ATS DNS local bind address.
 
 -c, -\-disable-parent-config-comments
 
-                    Whether to disable verbose parent.config comments. Default
-                    false.
+    Whether to disable verbose parent.config comments. Default
+    false.
 
 -C, -\-skip-os-check
 
-                    [false | true] skip os check, default is false
+    [false | true] skip os check, default is false
 
 -d, -\-no-unset-update-flag
 
-                    Whether to not unset the update flag in Traffic Ops after
-                    applying files. This option makes it possible to generate
-                    test or debug configuration from a production Traffic Ops
-                    without un-setting queue or reval flags. Default is false.
+    Whether to not unset the update flag in Traffic Ops after
+    applying files. This option makes it possible to generate
+    test or debug configuration from a production Traffic Ops
+    without un-setting queue or reval flags. Default is false.
 
 -e, -\-omit-via-string-release
 
-                    Whether to set the records.config via header to the ATS
-                    release from the RPM. Default true.
+    Whether to set the records.config via header to the ATS
+    release from the RPM. Default true.
 
 -E, -\-version
 
-                    Print version information and exit.
+    Print version information and exit.
 
 -f, -\-files=value  [all | reval]
 
-                    Which files to generate. If reval, the Traffic
-                    Ops server reval_pending flag is used instead of the
-                    upd_pending flag. Default is 'all'
+    Which files to generate. If reval, the Traffic
+    Ops server reval_pending flag is used instead of the
+    upd_pending flag. Default is 'all'.
 
 -F, -\-ignore-update-flag
 
-                    Whether to ignore the upd_pending or reval_pending flag in
-                    Traffic Ops, and always generate and apply files. If true,
-                    the flag is still unset in Traffic Ops after files are
-                    applied. Default is false.
+    Whether to ignore the upd_pending or reval_pending flag in
+    Traffic Ops, and always generate and apply files. If true,
+    the flag is still unset in Traffic Ops after files are
+    applied. Default is false.
 
 -g, -\-git=value
-                    Create and use a git repo in the config directory. Options
-                    are yes, no, and auto. If yes, create and use. If auto, use
-                    if it exist. Default is auto. [auto]
+
+    Create and use a git repo in the config directory. Options
+    are yes, no, and auto. If yes, create and use. If auto, use
+    if it exist. Default is auto. [auto]
 
 -H, -\-cache-host-name=value
 
-                    Host name of the cache to generate config for. Must be the
-                    server host name in Traffic Ops, not a URL, and not the FQDN
+    Host name of the cache to generate config for. Must be the
+    server host name in Traffic Ops, not a URL, and not the FQDN
 
 -h, -\-help
 
-                    Print usage information and exit
+    Print usage information and exit
 
 -i, -\-no-outgoing-ip
 
-     Whether to not set the records.config outgoing IP to the
-     server's addresses in Traffic Ops. Default is false.
+    Whether to not set the records.config outgoing IP to the
+    server's addresses in Traffic Ops. Default is false.
 
 -I, -\-traffic-ops-insecure
 
-                    [true | false] ignore certificate errors from Traffic Ops
+    [true | false] ignore certificate errors from Traffic Ops
 
 -k, -\-install-packages
 
-                    Whether to install necessary packages. Default is false.
+    Whether to install necessary packages. Default is false.
+
+-\-local-ats-version
+
+    [true | false] whether to use the local installed ATS version
+    for config generation. If false, attempt to use the Server
+    Package Parameter and fall back to ATS 5. If true and the
+    local ATS version cannot be found, an error will be logged
+    and the version set to ATS 5. Default is false.
 
 -M, -\-maxmind-location=value
 
-                    URL of a maxmind gzipped database file, to be installed into
-                    the trafficserver etc directory.
+    URL of a maxmind gzipped database file, to be installed into
+    the trafficserver etc directory.
 
 -m, -\-run-mode=value
 
-                    [badass | report | revalidate | syncds] run mode. Optional, convenience flag which sets other flags for common usage scenarios.
-                    syncds     keeps the defaults:
-                                    --report-only=false
-                                    --files=all
-                                    --install-packages=false
-                                    --service-action=reload
-                                    --ignore-update-flag=false
-                                    --update-ipallow=false
-                                    --no-unset-update-flag=false
-                    revalidate sets --files=reval
-                                    --wait-for-parents=true
-                    badass     sets --install-packages=true
-                                    --service-action=restart
-                                    --ignore-update-flag=true
-                                    --update-ipallow=true
-                    report     sets --report-only=true
-
-                    Note the 'syncds' settings are all the flag defaults. Hence, if no mode is set, the default is effectively 'syncds'.
-
-                    If any of the related flags are also set, they override the mode's default behavior.
-
- -n, -\-no-cache
+    [badass | report | revalidate | syncds] run mode. Optional,
+    convenience flag which sets other flags for common usage
+    scenarios.
+
+        syncds     keeps the defaults:
+                        --report-only=false
+                        --files=all
+                        --install-packages=false
+                        --service-action=reload
+                        --ignore-update-flag=false
+                        --update-ipallow=false
+                        --no-unset-update-flag=false
+
+        revalidate sets --files=reval
+                        --wait-for-parents=true
+
+        badass     sets --install-packages=true
+                        --service-action=restart
+                        --ignore-update-flag=true
+                        --update-ipallow=true
+
+        report     sets --report-only=true
+
+    Note the 'syncds' settings are all the flag defaults. Hence,
+    if no mode is set, the default is effectively 'syncds'.
+
+    If any of the related flags are also set, they override the
+    mode's default behavior.
+
+-n, -\-no-cache
 
     Whether to not use a cache and make conditional requests to
     Traffic Ops. Default is false: use cache.
 
 -o, -\-report-only
 
-                    Log information about necessary files and actions, but take
-                    no action. Default is false
+    Log information about necessary files and actions, but take
+    no action. Default is false
 
 -p, -\-reverse-proxy-disable
 
-                    [false | true] bypass the reverse proxy even if one has been
-                    configured default is false
+    [false | true] bypass the reverse proxy even if one has been
+    configured default is false
 
 -P, -\-traffic-ops-password=value
 
-                    Traffic Ops password. Required. May also be set with the
-                    environment variable TO_PASS
+    Traffic Ops password. Required. May also be set with the
+    environment variable TO_PASS
 
 -r, -\-num-retries=value
 
-                    [number] retry connection to Traffic Ops URL [number] times,
-                    default is 3 [3]
+    [number] retry connection to Traffic Ops URL [number] times,
+    default is 3 [3]
 
 -R, -\-trafficserver-home=value
 
-                    Trafficserver Package directory. May also be set with the
-                    environment variable TS_HOME
+    Trafficserver Package directory. May also be set with the
+    environment variable TS_HOME
 
 -s, -\-silent
 
-                    Silent. Errors are not logged, and the 'verbose' flag is
-                    ignored. If a fatal error occurs, the return code will be
-                    non-zero but no text will be output to stderr
+    Silent. Errors are not logged, and the 'verbose' flag is
+    ignored. If a fatal error occurs, the return code will be
+    non-zero but no text will be output to stderr
 
 -t, -\-traffic-ops-timeout-milliseconds=value
 
-                    Timeout in milli-seconds for Traffic Ops requests, default
-                    is 30000 [30000]
+    Timeout in milli-seconds for Traffic Ops requests, default
+    is 30000 [30000]
 
 -u, -\-traffic-ops-url=value
 
-                    Traffic Ops URL. Must be the full URL, including the scheme.
-                    Required. May also be set with the environment variable
-                    TO_URL
+    Traffic Ops URL. Must be the full URL, including the scheme.
+    Required. May also be set with the environment variable
+    TO_URL
 
 -U, -\-traffic-ops-user=value
 
-                    Traffic Ops username. Required. May also be set with the
-                    environment variable TO_USER
+    Traffic Ops username. Required. May also be set with the
+    environment variable TO_USER
 
 -V, -\-default-client-tls-versions=value
 
-                    Comma-delimited list of default TLS versions for Delivery
-                    Services with no Parameter, e.g.
-                    --default-tls-versions='1.1,1.2,1.3'. If omitted, all
-                    versions are enabled.
+    Comma-delimited list of default TLS versions for Delivery
+    Services with no Parameter, e.g.
+    --default-tls-versions='1.1,1.2,1.3'. If omitted, all
+    versions are enabled.
 
 -v, -\-verbose
 
-                    Log verbosity. Logging is output to stderr. By default,
-                    errors are logged. To log warnings, pass '-v'. To log info,
-                    pass '-vv'. To omit error logging, see '-s' [0]
+    Log verbosity. Logging is output to stderr. By default,
+    errors are logged. To log warnings, pass '-v'. To log info,
+    pass '-vv'. To omit error logging, see '-s' [0]
 
 -W, -\-wait-for-parents
 
-                    [true | false] do not update if parent_pending = 1 in the
-                    update json. Default is false
+    [true | false] do not update if parent_pending = 1 in the
+    update json. Default is false
 
 # MODES
 
diff --git a/cache-config/t3c-apply/config/config.go b/cache-config/t3c-apply/config/config.go
index 8d335630ac..edae35e8e8 100644
--- a/cache-config/t3c-apply/config/config.go
+++ b/cache-config/t3c-apply/config/config.go
@@ -26,6 +26,7 @@ import (
 	"net/url"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -116,6 +117,7 @@ type Cfg struct {
 	UpdateIPAllow     bool
 	Version           string
 	GitRevision       string
+	LocalATSVersion   string
 }
 
 func (cfg Cfg) AppVersion() string { return t3cutil.VersionStr(AppName, cfg.Version, cfg.GitRevision) }
@@ -278,6 +280,9 @@ func GetCfg(appVersion string, gitRevision string) (Cfg, error) {
 	const defaultUseStrategies = t3cutil.UseStrategiesFlagFalse
 	useStrategiesPtr := getopt.EnumLong(useStrategiesFlagName, 0, []string{string(t3cutil.UseStrategiesFlagTrue), string(t3cutil.UseStrategiesFlagCore), string(t3cutil.UseStrategiesFlagCore), ""}, "", "[true | core| false] whether to generate config using strategies.yaml instead of parent.config. If true use the parent_select plugin, if 'core' use ATS core strategies, if false use parent.config.")
 
+	const useLocalATSVersionFlagName = "local-ats-version"
+	useLocalATSVersionPtr := getopt.BoolLong(useLocalATSVersionFlagName, 0, "[true | false] whether to use the local installed ATS version for config generation. If false, attempt to use the Server Package Parameter and fall back to ATS 5. If true and the local ATS version cannot be found, an error will be logged and the version set to ATS 5. Default is false")
+
 	const runModeFlagName = "run-mode"
 	runModePtr := getopt.StringLong(runModeFlagName, 'm', "", `[badass | report | revalidate | syncds] run mode. Optional, convenience flag which sets other flags for common usage scenarios.
 syncds     keeps the defaults:
@@ -475,6 +480,15 @@ If any of the related flags are also set, they override the mode's default behav
 		toInfoLog = append(toInfoLog, fmt.Sprintf("TSHome: %s, TSConfigDir: %s\n", TSHome, tsConfigDir))
 	}
 
+	atsVersionStr := ""
+	if *useLocalATSVersionPtr {
+		atsVersionStr, err = GetATSVersionStr(tsHome)
+		if err != nil {
+			return Cfg{}, errors.New("getting local ATS version: " + err.Error())
+		}
+	}
+	toInfoLog = append(toInfoLog, fmt.Sprintf("ATSVersionStr: '%s'\n", atsVersionStr))
+
 	usageStr := "basic usage: t3c-apply --traffic-ops-url=myurl --traffic-ops-user=myuser --traffic-ops-password=mypass --cache-host-name=my-cache"
 	if strings.TrimSpace(toURL) == "" {
 		return Cfg{}, errors.New("Missing required argument --traffic-ops-url or TO_URL environment variable. " + usageStr)
@@ -539,6 +553,7 @@ If any of the related flags are also set, they override the mode's default behav
 		NoUnsetUpdateFlag: *noUnsetUpdateFlagPtr,
 		Version:           appVersion,
 		GitRevision:       gitRevision,
+		LocalATSVersion:   atsVersionStr,
 	}
 
 	if err = log.InitCfg(cfg); err != nil {
@@ -604,6 +619,22 @@ func getOSSvcManagement() SvcManagement {
 	return _svcManager
 }
 
+func GetATSVersionStr(tsHome string) (string, error) {
+	tsPath := tsHome
+	tsPath = filepath.Join(tsPath, "bin")
+	tsPath = filepath.Join(tsPath, "traffic_server")
+
+	stdOut, stdErr, code := t3cutil.Do(`sh`, `-c`, `set -o pipefail && `+tsPath+` --version | head -1 | awk '{print $3}'`)
+	if code != 0 {
+		return "", fmt.Errorf("traffic_server --version returned code %v stderr '%v' stdout '%v'", code, string(stdErr), string(stdOut))
+	}
+	atsVersion := strings.TrimSpace(string(stdOut))
+	if atsVersion == "" {
+		return "", fmt.Errorf("traffic_server --version returned nothing, code %v stderr '%v' stdout '%v'", code, string(stdErr), string(stdOut))
+	}
+	return atsVersion, nil
+}
+
 func printConfig(cfg Cfg) {
 	// TODO add new flags
 	log.Debugf("LogLocationDebug: %s\n", cfg.LogLocationDebug)
@@ -621,6 +652,7 @@ func printConfig(cfg Cfg) {
 	log.Debugf("TOPass: Pass len: '%d'\n", len(cfg.TOPass))
 	log.Debugf("TOURL: %s\n", cfg.TOURL)
 	log.Debugf("TSHome: %s\n", TSHome)
+	log.Debugf("LocalATSVersion: %s\n", cfg.LocalATSVersion)
 	log.Debugf("WaitForParents: %v\n", cfg.WaitForParents)
 	log.Debugf("YumOptions: %s\n", cfg.YumOptions)
 	log.Debugf("MaxmindLocation: %s\n", cfg.MaxMindLocation)
diff --git a/cache-config/t3c-apply/torequest/cmd.go b/cache-config/t3c-apply/torequest/cmd.go
index 85855d46d0..e5ec81438c 100644
--- a/cache-config/t3c-apply/torequest/cmd.go
+++ b/cache-config/t3c-apply/torequest/cmd.go
@@ -96,6 +96,9 @@ func generate(cfg config.Cfg) ([]t3cutil.ATSConfigFile, error) {
 	if cfg.Files == t3cutil.ApplyFilesFlagReval {
 		args = append(args, "--revalidate-only")
 	}
+	if cfg.LocalATSVersion != "" {
+		args = append(args, "--ats-version="+cfg.LocalATSVersion)
+	}
 	args = append(args, "--via-string-release="+strconv.FormatBool(!cfg.OmitViaStringRelease))
 	args = append(args, "--no-outgoing-ip="+strconv.FormatBool(cfg.NoOutgoingIP))
 	args = append(args, "--disable-parent-config-comments="+strconv.FormatBool(cfg.DisableParentConfigComments))
diff --git a/cache-config/t3c-generate/README.md b/cache-config/t3c-generate/README.md
index bbcf6c3ff7..db439ba750 100644
--- a/cache-config/t3c-generate/README.md
+++ b/cache-config/t3c-generate/README.md
@@ -61,6 +61,13 @@ The output is a JSON array of objects containing the file and its metadata.
     records.config is not serving H2. If omitted, H2 is
     disabled.
 
+-a, -\-ats-version
+
+    The ATS version, e.g. 9.1.2-42.abc123.el7.x86_64. If omitted
+    generation will attempt to get the ATS version from the
+    Server Profile Parameters, and fall back to
+    lib/go-atscfg.DefaultATSVersion.
+
 -b, -\-dns-local-bind
 
     Whether to use the server's Service Addresses to set the ATS
diff --git a/cache-config/t3c-generate/cfgfile/all.go b/cache-config/t3c-generate/cfgfile/all.go
index eb1c9ef698..8a04c0ab07 100644
--- a/cache-config/t3c-generate/cfgfile/all.go
+++ b/cache-config/t3c-generate/cfgfile/all.go
@@ -42,7 +42,7 @@ func GetAllConfigs(
 		return nil, errors.New("server hostname is nil")
 	}
 
-	configFiles, warnings, err := MakeConfigFilesList(toData, cfg.Dir)
+	configFiles, warnings, err := MakeConfigFilesList(toData, cfg.Dir, cfg.ATSMajorVersion)
 	logWarnings("generating config files list: ", warnings)
 	if err != nil {
 		return nil, errors.New("creating meta: " + err.Error())
diff --git a/cache-config/t3c-generate/cfgfile/wrappers.go b/cache-config/t3c-generate/cfgfile/wrappers.go
index 3f908ba0c0..894d58e6ea 100644
--- a/cache-config/t3c-generate/cfgfile/wrappers.go
+++ b/cache-config/t3c-generate/cfgfile/wrappers.go
@@ -26,16 +26,16 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 )
 
-//
 // This file has wrappers that turn lib/go-atscfg Make funcs into ConfigFileFunc types.
 //
 // We don't want to make lib/go-atscfg functions take a TOData, because then users wanting to generate a single file would have to fetch all kinds of data that file doesn't need, or else pass objects they know it doesn't currently need as nil and risk it crashing if that func is changed to use it in the future.
 //
 // But it's useful to map filenames to functions for dispatch. Hence these wrappers.
 //
-
+// The atsMajorVersion may be 0 to default to the Server Package Parameter.
+//
 // MakeConfigFilesList returns the list of config files, any warnings, and any error.
-func MakeConfigFilesList(toData *t3cutil.ConfigData, dir string) ([]atscfg.CfgMeta, []string, error) {
+func MakeConfigFilesList(toData *t3cutil.ConfigData, dir string, atsMajorVersion uint) ([]atscfg.CfgMeta, []string, error) {
 	configFiles, warnings, err := atscfg.MakeConfigFilesList(
 		dir,
 		toData.Server,
@@ -45,7 +45,9 @@ func MakeConfigFilesList(toData *t3cutil.ConfigData, dir string) ([]atscfg.CfgMe
 		toData.GlobalParams,
 		toData.CacheGroups,
 		toData.Topologies,
-		&atscfg.ConfigFilesListOpts{},
+		&atscfg.ConfigFilesListOpts{
+			ATSMajorVersion: atsMajorVersion,
+		},
 	)
 	return configFiles, warnings, err
 }
@@ -119,8 +121,14 @@ func MakeLoggingDotConfig(toData *t3cutil.ConfigData, fileName string, hdrCommen
 }
 
 func MakeLoggingDotYAML(toData *t3cutil.ConfigData, fileName string, hdrCommentTxt string, cfg config.Cfg) (atscfg.Cfg, error) {
-	opts := &atscfg.LoggingDotYAMLOpts{HdrComment: hdrCommentTxt}
-	return atscfg.MakeLoggingDotYAML(toData.Server, toData.ServerParams, opts)
+	return atscfg.MakeLoggingDotYAML(
+		toData.Server,
+		toData.ServerParams,
+		&atscfg.LoggingDotYAMLOpts{
+			HdrComment:      hdrCommentTxt,
+			ATSMajorVersion: cfg.ATSMajorVersion,
+		},
+	)
 }
 
 func MakeSSLServerNameYAML(toData *t3cutil.ConfigData, fileName string, hdrCommentTxt string, cfg config.Cfg) (atscfg.Cfg, error) {
@@ -188,8 +196,9 @@ func MakeParentDotConfig(toData *t3cutil.ConfigData, fileName string, hdrComment
 		toData.DeliveryServiceServers,
 		toData.CDN,
 		&atscfg.ParentConfigOpts{
-			HdrComment:  hdrCommentTxt,
-			AddComments: cfg.ParentComments, // TODO add a CLI flag?
+			HdrComment:      hdrCommentTxt,
+			AddComments:     cfg.ParentComments, // TODO add a CLI flag?
+			ATSMajorVersion: cfg.ATSMajorVersion,
 		},
 	)
 }
@@ -239,6 +248,7 @@ func MakeRemapDotConfig(toData *t3cutil.ConfigData, fileName string, hdrCommentT
 			VerboseComments:   true,
 			UseStrategies:     cfg.UseStrategies == t3cutil.UseStrategiesFlagTrue || cfg.UseStrategies == t3cutil.UseStrategiesFlagCore,
 			UseStrategiesCore: cfg.UseStrategies == t3cutil.UseStrategiesFlagCore,
+			ATSMajorVersion:   cfg.ATSMajorVersion,
 		},
 	)
 }
@@ -264,7 +274,6 @@ func MakeVolumeDotConfig(toData *t3cutil.ConfigData, fileName string, hdrComment
 }
 
 func MakeHeaderRewrite(toData *t3cutil.ConfigData, fileName string, hdrCommentTxt string, cfg config.Cfg) (atscfg.Cfg, error) {
-	opts := &atscfg.HeaderRewriteDotConfigOpts{HdrComment: hdrCommentTxt}
 	return atscfg.MakeHeaderRewriteDotConfig(
 		fileName,
 		toData.DeliveryServices,
@@ -276,7 +285,10 @@ func MakeHeaderRewrite(toData *t3cutil.ConfigData, fileName string, hdrCommentTx
 		toData.ServerCapabilities,
 		toData.DSRequiredCapabilities,
 		toData.Topologies,
-		opts,
+		&atscfg.HeaderRewriteDotConfigOpts{
+			HdrComment:      hdrCommentTxt,
+			ATSMajorVersion: cfg.ATSMajorVersion,
+		},
 	)
 }
 
@@ -315,6 +327,7 @@ func MakeStrategiesDotYAML(toData *t3cutil.ConfigData, fileName string, hdrComme
 		&atscfg.StrategiesYAMLOpts{
 			HdrComment:      hdrCommentTxt,
 			VerboseComments: cfg.ParentComments, // TODO add a CLI flag?
+			ATSMajorVersion: cfg.ATSMajorVersion,
 		},
 	)
 }
diff --git a/cache-config/t3c-generate/config/config.go b/cache-config/t3c-generate/config/config.go
index 765df743dc..338c0889c5 100644
--- a/cache-config/t3c-generate/config/config.go
+++ b/cache-config/t3c-generate/config/config.go
@@ -55,6 +55,7 @@ type Cfg struct {
 	ViaRelease         bool
 	SetDNSLocalBind    bool
 	NoOutgoingIP       bool
+	ATSMajorVersion    uint
 	ParentComments     bool
 	DefaultEnableH2    bool
 	DefaultTLSVersions []atscfg.TLSVersion
@@ -83,6 +84,7 @@ func GetCfg(appVersion string, gitRevision string) (Cfg, error) {
 	defaultEnableH2 := getopt.BoolLong("default-client-enable-h2", '2', "Whether to enable HTTP/2 on Delivery Services by default, if they have no explicit Parameter. This is irrelevant if ATS records.config is not serving H2. If omitted, H2 is disabled.")
 	defaultTLSVersionsStr := getopt.StringLong("default-client-tls-versions", 'T', "", "Comma-delimited list of default TLS versions for Delivery Services with no Parameter, e.g. '--default-tls-versions=1.1,1.2,1.3'. If omitted, all versions are enabled.")
 	noOutgoingIP := getopt.BoolLong("no-outgoing-ip", 'i', "Whether to not set the records.config outgoing IP to the server's addresses in Traffic Ops. Default is false.")
+	atsVersion := getopt.StringLong("ats-version", 'a', "", "The ATS version, e.g. 9.1.2-42.abc123.el7.x86_64. If omitted, generation will attempt to get the ATS version from the Server Parameters, and fall back to lib/go-atscfg.DefaultATSVersion")
 	verbosePtr := getopt.CounterLong("verbose", 'v', `Log verbosity. Logging is output to stderr. By default, errors are logged. To log warnings, pass '-v'. To log info, pass '-vv'. To omit error logging, see '-s'`)
 	silentPtr := getopt.BoolLong("silent", 's', `Silent. Errors are not logged, and the 'verbose' flag is ignored. If a fatal error occurs, the return code will be non-zero but no text will be output to stderr`)
 
@@ -123,6 +125,17 @@ func GetCfg(appVersion string, gitRevision string) (Cfg, error) {
 		return Cfg{}, errors.New("Too many verbose options. The maximum log verbosity level is 2 (-vv or --verbose=2) for errors (0), warnings (1), and info (2)")
 	}
 
+	// The flag takes the full version, for forward-compatibility in case we need it in the future,
+	// but we only need the major version at the moment.
+	atsMajorVersion := uint(0)
+	if *atsVersion != "" {
+		err := error(nil)
+		atsMajorVersion, err = atscfg.GetATSMajorVersionFromATSVersion(*atsVersion)
+		if err != nil {
+			return Cfg{}, errors.New("parsing ATS version '" + *atsVersion + "': " + err.Error())
+		}
+	}
+
 	defaultTLSVersions := atscfg.DefaultDefaultTLSVersions
 
 	*defaultTLSVersionsStr = strings.TrimSpace(*defaultTLSVersionsStr)
@@ -154,6 +167,7 @@ func GetCfg(appVersion string, gitRevision string) (Cfg, error) {
 		ViaRelease:         *viaRelease,
 		SetDNSLocalBind:    *dnsLocalBind,
 		NoOutgoingIP:       *noOutgoingIP,
+		ATSMajorVersion:    atsMajorVersion,
 		ParentComments:     !(*disableParentConfigComments),
 		DefaultEnableH2:    *defaultEnableH2,
 		DefaultTLSVersions: defaultTLSVersions,
diff --git a/lib/go-atscfg/atscfg.go b/lib/go-atscfg/atscfg.go
index 92abf8a09e..45287c3f91 100644
--- a/lib/go-atscfg/atscfg.go
+++ b/lib/go-atscfg/atscfg.go
@@ -226,21 +226,21 @@ func makeHdrComment(hdrComment string) string {
 	return "# " + hdrComment + "\n\n"
 }
 
-// getATSMajorVersionFromATSVersion returns the major version of the given profile's package trafficserver parameter.
+// GetATSMajorVersionFromATSVersion returns the major version of the given profile's package trafficserver parameter.
 // The atsVersion is typically a Parameter on the Server's Profile, with the configFile "package" name "trafficserver".
 // Returns an error if atsVersion is empty or does not start with an unsigned integer followed by a period or nothing.
-func getATSMajorVersionFromATSVersion(atsVersion string) (int, error) {
+func GetATSMajorVersionFromATSVersion(atsVersion string) (uint, error) {
 	dotPos := strings.Index(atsVersion, ".")
 	if dotPos == -1 {
 		dotPos = len(atsVersion) // if there's no '.' then assume the whole string is just a major version.
 	}
 	majorVerStr := atsVersion[:dotPos]
 
-	majorVer, err := strconv.Atoi(majorVerStr)
-	if err != nil || majorVer < 0 {
+	majorVer, err := strconv.ParseUint(majorVerStr, 10, 64)
+	if err != nil || majorVer == 0 || majorVer > 9999 {
 		return 0, errors.New("unexpected version format '" + majorVerStr + "', expected e.g. '7.1.2.whatever'")
 	}
-	return majorVer, nil
+	return uint(majorVer), nil
 }
 
 // genericProfileConfig generates a generic profile config text, from the profile's parameters with the given config file name.
@@ -617,10 +617,28 @@ func getServiceAddresses(sv *Server) (net.IP, net.IP) {
 	return v4, v6
 }
 
-// getATSMajorVersion returns the ATS major version from the config_file 'package' name 'trafficserver' Parameter on the given Server Profile Parameters.
+// getATSMajorVersion takes a config variable of the version, the Server Parameters, and a pointer to warnings to populate.
+// This allows callers to use the config variable if it was given, or get the ATS version from the Server Parameters if it wasn't, and add to a warnings variable, in a single line.
+//
+// If more flexibility is needed, getATSMajorVersionFromParams may be called directly;
+// but it should generally be avoided, functions should always take a config variable for the
+// ATS version, in case a user wants to manage the ATS package outside ATC.
+func getATSMajorVersion(atsMajorVersion uint, serverParams []tc.Parameter, warnings *[]string) uint {
+	if atsMajorVersion != 0 {
+		return atsMajorVersion
+	}
+	verWarns := []string{}
+	atsMajorVersion, verWarns = getATSMajorVersionFromParams(serverParams)
+	*warnings = append(*warnings, verWarns...)
+	return atsMajorVersion
+}
+
+// getATSMajorVersionFromParams should generally not be called directly. Rather, functions should always take the version as a config parameter which may be omitted, and call getATSMajorVersion.
+//
+// It returns the ATS major version from the config_file 'package' name 'trafficserver' Parameter on the given Server Profile Parameters.
 // If no Parameter is found, or the value is malformed, a warning or error is logged and DefaultATSVersion is returned.
 // Returns the ATS major version, and any warnings
-func getATSMajorVersion(serverParams []tc.Parameter) (int, []string) {
+func getATSMajorVersionFromParams(serverParams []tc.Parameter) (uint, []string) {
 	warnings := []string{}
 	atsVersionParam := ""
 	for _, param := range serverParams {
@@ -635,10 +653,10 @@ func getATSMajorVersion(serverParams []tc.Parameter) (int, []string) {
 		atsVersionParam = DefaultATSVersion
 	}
 
-	atsMajorVer, err := getATSMajorVersionFromATSVersion(atsVersionParam)
+	atsMajorVer, err := GetATSMajorVersionFromATSVersion(atsVersionParam)
 	if err != nil {
 		warnings = append(warnings, "getting ATS major version from server Profile Parameter, using default: "+err.Error())
-		atsMajorVer, err = getATSMajorVersionFromATSVersion(DefaultATSVersion)
+		atsMajorVer, err = GetATSMajorVersionFromATSVersion(DefaultATSVersion)
 		if err != nil {
 			// should never happen
 			warnings = append(warnings, "getting ATS major version from default version! Should never happen! Using 0, config will be malformed! : "+err.Error())
diff --git a/lib/go-atscfg/atscfg_test.go b/lib/go-atscfg/atscfg_test.go
index b25c04dbc6..db0f6bb523 100644
--- a/lib/go-atscfg/atscfg_test.go
+++ b/lib/go-atscfg/atscfg_test.go
@@ -76,7 +76,7 @@ func TestTrimParamUnderscoreNumSuffix(t *testing.T) {
 }
 
 func TestGetATSMajorVersionFromATSVersion(t *testing.T) {
-	inputExpected := map[string]int{
+	inputExpected := map[string]uint{
 		`7.1.2-34.56abcde.el7.centos.x86_64`:    7,
 		`8`:                                     8,
 		`8.1`:                                   8,
@@ -98,14 +98,14 @@ func TestGetATSMajorVersionFromATSVersion(t *testing.T) {
 	}
 
 	for input, expected := range inputExpected {
-		if actual, err := getATSMajorVersionFromATSVersion(input); err != nil {
+		if actual, err := GetATSMajorVersionFromATSVersion(input); err != nil {
 			t.Errorf("expected %v actual: error '%v'", expected, err)
 		} else if actual != expected {
 			t.Errorf("expected %v actual: %v", expected, actual)
 		}
 	}
 	for _, input := range errExpected {
-		if actual, err := getATSMajorVersionFromATSVersion(input); err == nil {
+		if actual, err := GetATSMajorVersionFromATSVersion(input); err == nil {
 			t.Errorf("input %v expected: error, actual: nil error '%v'", input, actual)
 		}
 	}
diff --git a/lib/go-atscfg/headerrewritedotconfig.go b/lib/go-atscfg/headerrewritedotconfig.go
index ff1c509e62..7cadd0bd06 100644
--- a/lib/go-atscfg/headerrewritedotconfig.go
+++ b/lib/go-atscfg/headerrewritedotconfig.go
@@ -63,6 +63,14 @@ type HeaderRewriteDotConfigOpts struct {
 	// This should be the text desired, without comment syntax (like # or //). The file's comment syntax will be added.
 	// To omit the header comment, pass the empty string.
 	HdrComment string
+
+	// ATSMajorVersion is the integral major version of Apache Traffic server,
+	// used to generate the proper config for the proper version.
+	//
+	// If omitted or 0, the major version will be read from the Server's Profile Parameter config file 'package' name 'trafficserver'. If no such Parameter exists, the ATS version will default to 5.
+	// This was the old Traffic Control behavior, before the version was specifiable externally.
+	//
+	ATSMajorVersion uint
 }
 
 // MakeHeaderRewriteDotConfig makes the header rewrite file for
@@ -151,8 +159,7 @@ func MakeHeaderRewriteDotConfig(
 	atsRqstMaxHdrSize, paramWarns := getMaxRequestHeaderParam(tcServerParams)
 	warnings = append(warnings, paramWarns...)
 
-	atsMajorVersion, verWarns := getATSMajorVersion(tcServerParams)
-	warnings = append(warnings, verWarns...)
+	atsMajorVersion := getATSMajorVersion(opt.ATSMajorVersion, tcServerParams, &warnings)
 
 	assignedTierPeers, assignWarns := getAssignedTierPeers(server, ds, topology, servers, deliveryServiceServers, cacheGroupsArr, serverCapabilities, requiredCapabilities[*ds.ID])
 	warnings = append(warnings, assignWarns...)
@@ -530,13 +537,13 @@ var returnRe = regexp.MustCompile(`\s*__RETURN__\s*`)
 //	If they're placed after, custom rewrites with [L] directives will result in them being applied inconsistently and incorrectly.
 //
 // The headerRewriteTxt is the custom header rewrite from the Delivery Service. This should be used for any logic that depends on it. The various header rewrite fields (EdgeHeaderRewrite, InnerHeaderRewrite, etc should never be used inside this function, since this function doesn't know what tier the server is at. This function should not insert the headerRewriteText, but may use it to make decisions about what to insert.
-func makeATCHeaderRewriteDirectives(ds *DeliveryService, headerRewriteTxt *string, serverIsLastTier bool, numLastTierServers int, atsMajorVersion int, atsRqstMaxHdrSize int) string {
+func makeATCHeaderRewriteDirectives(ds *DeliveryService, headerRewriteTxt *string, serverIsLastTier bool, numLastTierServers int, atsMajorVersion uint, atsRqstMaxHdrSize int) string {
 	return makeATCHeaderRewriteDirectiveMaxOriginConns(ds, headerRewriteTxt, serverIsLastTier, numLastTierServers, atsMajorVersion) +
 		makeATCHeaderRewriteDirectiveServiceCategoryHdr(ds, headerRewriteTxt) + makeATCHeaderRewriteDirectiveMaxRequestHeaderSize(ds, serverIsLastTier, atsRqstMaxHdrSize)
 }
 
 // makeATCHeaderRewriteDirectiveMaxOriginConns generates the Max Origin Connections header rewrite text, which may be empty.
-func makeATCHeaderRewriteDirectiveMaxOriginConns(ds *DeliveryService, headerRewriteTxt *string, serverIsLastTier bool, numLastTierServers int, atsMajorVersion int) string {
+func makeATCHeaderRewriteDirectiveMaxOriginConns(ds *DeliveryService, headerRewriteTxt *string, serverIsLastTier bool, numLastTierServers int, atsMajorVersion uint) string {
 	if !serverIsLastTier ||
 		(ds.MaxOriginConnections == nil || *ds.MaxOriginConnections < 1) ||
 		numLastTierServers < 1 {
diff --git a/lib/go-atscfg/loggingdotyaml.go b/lib/go-atscfg/loggingdotyaml.go
index a675907d10..359a1ea3ea 100644
--- a/lib/go-atscfg/loggingdotyaml.go
+++ b/lib/go-atscfg/loggingdotyaml.go
@@ -37,15 +37,23 @@ type LoggingDotYAMLOpts struct {
 	// This should be the text desired, without comment syntax (like # or //). The file's comment syntax will be added.
 	// To omit the header comment, pass the empty string.
 	HdrComment string
+
+	// ATSMajorVersion is the integral major version of Apache Traffic server,
+	// used to generate the proper config for the proper version.
+	//
+	// If omitted or 0, the major version will be read from the Server's Profile Parameter config file 'package' name 'trafficserver'. If no such Parameter exists, the ATS version will default to 5.
+	// This was the old Traffic Control behavior, before the version was specifiable externally.
+	//
+	ATSMajorVersion uint
 }
 
 func MakeLoggingDotYAML(
 	server *Server,
 	serverParams []tc.Parameter,
-	opts *LoggingDotYAMLOpts,
+	opt *LoggingDotYAMLOpts,
 ) (Cfg, error) {
-	if opts == nil {
-		opts = &LoggingDotYAMLOpts{}
+	if opt == nil {
+		opt = &LoggingDotYAMLOpts{}
 	}
 	warnings := []string{}
 	requiredIndent := 0
@@ -61,15 +69,15 @@ func MakeLoggingDotYAML(
 	paramData, paramWarns := paramsToMap(filterParams(serverParams, LoggingYAMLFileName, "", "", "location"))
 	warnings = append(warnings, paramWarns...)
 
-	hdr := makeHdrComment(opts.HdrComment)
+	hdr := makeHdrComment(opt.HdrComment)
+
+	atsMajorVersion := getATSMajorVersion(opt.ATSMajorVersion, serverParams, &warnings)
 
-	version, vWarn := getATSMajorVersion(serverParams)
-	warnings = append(warnings, vWarn...)
 	// note we use the same const as logs.xml - this isn't necessarily a requirement, and we may want to make separate variables in the future.
 	maxLogObjects := MaxLogObjects
 
 	text := hdr
-	if version >= 9 {
+	if atsMajorVersion >= 9 {
 		text += "\nlogging:"
 		requiredIndent += 2
 	}
diff --git a/lib/go-atscfg/meta.go b/lib/go-atscfg/meta.go
index cbd23aa99d..aac7b7df45 100644
--- a/lib/go-atscfg/meta.go
+++ b/lib/go-atscfg/meta.go
@@ -34,6 +34,13 @@ type CfgMeta struct {
 
 // ConfigFilesListOpts contains settings to configure generation options.
 type ConfigFilesListOpts struct {
+	// ATSMajorVersion is the integral major version of Apache Traffic server,
+	// used to generate the proper config for the proper version.
+	//
+	// If omitted or 0, the major version will be read from the Server's Profile Parameter config file 'package' name 'trafficserver'. If no such Parameter exists, the ATS version will default to 5.
+	// This was the old Traffic Control behavior, before the version was specifiable externally.
+	//
+	ATSMajorVersion uint
 }
 
 // MakeMetaObj returns the list of config files, any warnings, and any errors.
@@ -71,8 +78,7 @@ func MakeConfigFilesList(
 		return nil, warnings, errors.New("server missing Profile")
 	}
 
-	atsMajorVer, verWarns := getATSMajorVersion(serverParams)
-	warnings = append(warnings, verWarns...)
+	atsMajorVersion := getATSMajorVersion(opt.ATSMajorVersion, serverParams, &warnings)
 
 	dses, dsWarns := filterConfigFileDSes(server, deliveryServices, deliveryServiceServers)
 	warnings = append(warnings, dsWarns...)
@@ -129,7 +135,7 @@ locationParamsFor:
 		configFiles = append(configFiles, atsCfg)
 	}
 
-	configFiles, configDirWarns, err := addMetaObjConfigDir(configFiles, configDir, server, locationParams, uriSignedDSes, dses, cacheGroupArr, topologies, atsMajorVer)
+	configFiles, configDirWarns, err := addMetaObjConfigDir(configFiles, configDir, server, locationParams, uriSignedDSes, dses, cacheGroupArr, topologies, atsMajorVersion)
 	warnings = append(warnings, configDirWarns...)
 	return configFiles, warnings, err
 }
@@ -148,7 +154,7 @@ func addMetaObjConfigDir(
 	dses map[tc.DeliveryServiceName]DeliveryService,
 	cacheGroupArr []tc.CacheGroupNullable,
 	topologies []tc.Topology,
-	atsMajorVer int,
+	atsMajorVersion uint,
 ) ([]CfgMeta, []string, error) {
 	warnings := []string{}
 
@@ -171,7 +177,7 @@ func addMetaObjConfigDir(
 	// If they don't exist, create them.
 	// If they exist with a relative path, prepend configDir.
 	// If any exist with a relative path, or don't exist, and configDir is empty, return an error.
-	for _, fileName := range requiredFiles(atsMajorVer) {
+	for _, fileName := range requiredFiles(atsMajorVersion) {
 		if _, ok := configFilesM[fileName]; ok {
 			continue
 		}
@@ -413,8 +419,8 @@ type configProfileParams struct {
 	Path string
 }
 
-func requiredFiles(atsMajorVer int) []string {
-	if atsMajorVer >= 9 {
+func requiredFiles(atsMajorVersion uint) []string {
+	if atsMajorVersion >= 9 {
 		return requiredFiles9()
 	}
 	return requiredFiles8()
diff --git a/lib/go-atscfg/parentabstraction.go b/lib/go-atscfg/parentabstraction.go
index c79310b268..dab87aa27f 100644
--- a/lib/go-atscfg/parentabstraction.go
+++ b/lib/go-atscfg/parentabstraction.go
@@ -273,7 +273,7 @@ var DefaultUnavailableServerRetryCodes = []int{503}
 
 const DefaultIgnoreQueryStringInParentSelection = false
 
-func parentAbstractionToParentDotConfig(pa *ParentAbstraction, opt *ParentConfigOpts, atsMajorVersion int) (string, []string, error) {
+func parentAbstractionToParentDotConfig(pa *ParentAbstraction, opt *ParentConfigOpts, atsMajorVersion uint) (string, []string, error) {
 	warnings := []string{}
 	txt := ""
 
@@ -301,7 +301,7 @@ func parentAbstractionToParentDotConfig(pa *ParentAbstraction, opt *ParentConfig
 	return txt, warnings, nil
 }
 
-func (svc *ParentAbstractionService) ToParentDotConfigLine(opt *ParentConfigOpts, atsMajorVersion int) (string, []string, error) {
+func (svc *ParentAbstractionService) ToParentDotConfigLine(opt *ParentConfigOpts, atsMajorVersion uint) (string, []string, error) {
 	warnings := []string{}
 	txt := ""
 	if opt.AddComments && svc.Comment != "" {
@@ -371,7 +371,7 @@ func (svc *ParentAbstractionService) ToParentDotConfigLine(opt *ParentConfigOpts
 		if atsMajorVersion >= 9 {
 			txt += ` simple_server_retry_responses="` + strings.Join(intsToStrs(svc.ErrorResponseCodes), `,`) + `"`
 		} else {
-			warnings = append(warnings, "Service '"+svc.Name+"' had simple retry codes '"+strings.Join(intsToStrs(svc.ErrorResponseCodes), ",")+"' but ATS version "+strconv.Itoa(atsMajorVersion)+" < 9 does not support custom simple retry codes, omitting!")
+			warnings = append(warnings, "Service '"+svc.Name+"' had simple retry codes '"+strings.Join(intsToStrs(svc.ErrorResponseCodes), ",")+"' but ATS version "+strconv.FormatUint(uint64(atsMajorVersion), 10)+" < 9 does not support custom simple retry codes, omitting!")
 		}
 	}
 
diff --git a/lib/go-atscfg/parentdotconfig.go b/lib/go-atscfg/parentdotconfig.go
index 8eabf1a20b..2de9d97f8f 100644
--- a/lib/go-atscfg/parentdotconfig.go
+++ b/lib/go-atscfg/parentdotconfig.go
@@ -100,6 +100,14 @@ type ParentConfigOpts struct {
 	// This should be the text desired, without comment syntax (like # or //). The file's comment syntax will be added.
 	// To omit the header comment, pass the empty string.
 	HdrComment string
+
+	// ATSMajorVersion is the integral major version of Apache Traffic server,
+	// used to generate the proper config for the proper version.
+	//
+	// If omitted or 0, the major version will be read from the Server's Profile Parameter config file 'package' name 'trafficserver'. If no such Parameter exists, the ATS version will default to 5.
+	// This was the old Traffic Control behavior, before the version was specifiable externally.
+	//
+	ATSMajorVersion uint
 }
 
 func MakeParentDotConfig(
@@ -116,7 +124,10 @@ func MakeParentDotConfig(
 	cdn *tc.CDN,
 	opt *ParentConfigOpts,
 ) (Cfg, error) {
-	parentAbstraction, warnings, err := makeParentDotConfigData(
+	warnings := []string{}
+	atsMajorVersion := getATSMajorVersion(opt.ATSMajorVersion, tcServerParams, &warnings)
+
+	parentAbstraction, dataWarns, err := makeParentDotConfigData(
 		dses,
 		server,
 		servers,
@@ -129,15 +140,14 @@ func MakeParentDotConfig(
 		dss,
 		cdn,
 		opt,
+		atsMajorVersion,
 	)
+	warnings = append(warnings, dataWarns...)
 	if err != nil {
 		return Cfg{}, makeErr(warnings, err.Error())
 	}
 
-	atsMajorVer, verWarns := getATSMajorVersion(tcServerParams)
-	warnings = append(warnings, verWarns...)
-
-	text, paWarns, err := parentAbstractionToParentDotConfig(parentAbstraction, opt, atsMajorVer)
+	text, paWarns, err := parentAbstractionToParentDotConfig(parentAbstraction, opt, atsMajorVersion)
 	warnings = append(warnings, paWarns...)
 	if err != nil {
 		return Cfg{}, makeErr(warnings, err.Error())
@@ -169,6 +179,7 @@ func makeParentDotConfigData(
 	dss []DeliveryServiceServer,
 	cdn *tc.CDN,
 	opt *ParentConfigOpts,
+	atsMajorVersion uint,
 ) (*ParentAbstraction, []string, error) {
 	if opt == nil {
 		opt = &ParentConfigOpts{}
@@ -188,10 +199,6 @@ func makeParentDotConfigData(
 		return nil, warnings, errors.New("server TCPPort missing")
 	}
 
-	// TODO remove, the abstraction shouldn't depend on the ATS version
-	atsMajorVer, verWarns := getATSMajorVersion(tcServerParams)
-	warnings = append(warnings, verWarns...)
-
 	cacheGroups, err := makeCGMap(cacheGroupArr)
 	if err != nil {
 		return nil, warnings, errors.New("making CacheGroup map: " + err.Error())
@@ -393,7 +400,7 @@ func makeParentDotConfigData(
 				cacheGroups,
 				profileParentConfigParams,
 				isMSO,
-				atsMajorVer,
+				atsMajorVersion,
 				dsOrigins[DeliveryServiceID(*ds.ID)],
 				opt.AddComments,
 			)
@@ -499,7 +506,7 @@ func makeParentDotConfigData(
 						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)
+					parents, secondaryParents, secondaryMode, parentWarns := getMSOParentStrs(&ds, parentInfos[OriginHost(orgURI.Hostname())], atsMajorVersion, dsParams.Algorithm, dsParams.TryAllPrimariesBeforeSecondary)
 					warnings = append(warnings, parentWarns...)
 					pasvc.Parents = parents
 					pasvc.SecondaryParents = secondaryParents
@@ -510,7 +517,7 @@ func makeParentDotConfigData(
 
 					// textLine += parents + secondaryParents + ` round_robin=` + dsParams.Algorithm + ` qstring=` + parentQStr + ` go_direct=false parent_is_proxy=false`
 
-					prWarns := dsParams.FillParentSvcRetries(cacheIsTopLevel, atsMajorVer, pasvc)
+					prWarns := dsParams.FillParentSvcRetries(cacheIsTopLevel, atsMajorVersion, pasvc)
 					warnings = append(warnings, prWarns...)
 
 					parentAbstraction.Services = append(parentAbstraction.Services, pasvc)
@@ -526,7 +533,7 @@ func makeParentDotConfigData(
 				goDirect := false
 				// goDirect := `go_direct=false`
 
-				parents, secondaryParents, secondaryMode, parentWarns := getParentStrs(&ds, dsRequiredCapabilities, parentInfos[deliveryServicesAllParentsKey], atsMajorVer, dsParams.TryAllPrimariesBeforeSecondary)
+				parents, secondaryParents, secondaryMode, parentWarns := getParentStrs(&ds, dsRequiredCapabilities, parentInfos[deliveryServicesAllParentsKey], atsMajorVersion, dsParams.TryAllPrimariesBeforeSecondary)
 				warnings = append(warnings, parentWarns...)
 
 				pasvc := &ParentAbstractionService{}
@@ -624,7 +631,7 @@ func makeParentDotConfigData(
 					// text += `dest_domain=` + orgURI.Hostname() + ` port=` + orgURI.Port() + ` ` + parents + ` ` + secondaryParents + ` ` + roundRobin + ` ` + goDirect + ` qstring=` + parentQStr + "\n"
 				}
 
-				prWarns := dsParams.FillParentSvcRetries(cacheIsTopLevel, atsMajorVer, pasvc)
+				prWarns := dsParams.FillParentSvcRetries(cacheIsTopLevel, atsMajorVersion, pasvc)
 				warnings = append(warnings, prWarns...)
 
 				parentAbstraction.Services = append(parentAbstraction.Services, pasvc)
@@ -641,7 +648,7 @@ func makeParentDotConfigData(
 		invalidDS := &DeliveryService{}
 		invalidDS.ID = util.IntPtr(-1)
 		tryAllPrimariesBeforeSecondary := false
-		parents, secondaryParents, secondaryMode, parentWarns := getParentStrs(invalidDS, dsRequiredCapabilities, parentInfos[deliveryServicesAllParentsKey], atsMajorVer, tryAllPrimariesBeforeSecondary)
+		parents, secondaryParents, secondaryMode, parentWarns := getParentStrs(invalidDS, dsRequiredCapabilities, parentInfos[deliveryServicesAllParentsKey], atsMajorVersion, tryAllPrimariesBeforeSecondary)
 		warnings = append(warnings, parentWarns...)
 
 		defaultDestText.DestDomain = `.`
@@ -1001,7 +1008,7 @@ func getTopologyParentConfigLine(
 	cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable,
 	profileParentConfigParams map[string]map[string]string,
 	isMSO bool,
-	atsMajorVer int,
+	atsMajorVersion uint,
 	dsOrigins map[ServerID]struct{},
 	addComments bool,
 ) (*ParentAbstractionService, []string, error) {
@@ -1070,7 +1077,7 @@ func getTopologyParentConfigLine(
 		pasvc.SecondaryParents = secondaryParents
 		// txt += ` secondary_parent="` + strings.Join(secondaryParents, `;`) + `"`
 
-		secondaryModeStr, secondaryModeWarnings := getSecondaryModeStr(dsParams.TryAllPrimariesBeforeSecondary, atsMajorVer, tc.DeliveryServiceName(*ds.XMLID))
+		secondaryModeStr, secondaryModeWarnings := getSecondaryModeStr(dsParams.TryAllPrimariesBeforeSecondary, atsMajorVersion, tc.DeliveryServiceName(*ds.XMLID))
 		warnings = append(warnings, secondaryModeWarnings...)
 		// txt += secondaryModeStr
 		pasvc.SecondaryMode = secondaryModeStr // TODO convert
@@ -1103,7 +1110,7 @@ func getTopologyParentConfigLine(
 	// txt += getTopologyParentIsProxyStr(serverPlacement.IsLastCacheTier)
 
 	// TODO convert
-	prWarns := dsParams.FillParentSvcRetries(serverPlacement.IsLastCacheTier, atsMajorVer, pasvc)
+	prWarns := dsParams.FillParentSvcRetries(serverPlacement.IsLastCacheTier, atsMajorVersion, pasvc)
 	warnings = append(warnings, prWarns...)
 
 	// txt += getParentRetryStr(serverPlacement.IsLastCacheTier, atsMajorVer, dsParams.ParentRetry, dsParams.UnavailableServerRetryResponses, dsParams.MaxSimpleRetries, dsParams.MaxUnavailableServerRetries)
@@ -1120,7 +1127,7 @@ func getTopologyParentConfigLine(
 //
 // Returns the MaxSimpleRetries, MaxMarkdownRetries, ErrorREsponseCodes, MarkdownResponseCodes.
 //
-// If atsMajorVer < 6, "" is returned (ATS 5 and below don't support retry directives).
+// If atsMajorVersion < 6, "" is returned (ATS 5 and below don't support retry directives).
 // If isLastCacheTier is false, "" is returned. This argument exists to simplify usage.
 // If parentRetry is "", "" is returned (because the other directives are unused if parent_retry doesn't exist). This is allowed to simplify usage.
 // If unavailableServerRetryResponses is not "", it must be valid. Use unavailableServerRetryResponsesValid to check.
@@ -1128,11 +1135,11 @@ func getTopologyParentConfigLine(
 // If maxUnavailableServerRetries is "", ParentConfigDSParamDefaultMaxUnavailableServerRetries will be used.
 //
 // Does not return errors. If any input is malformed, warnings are returned and that value is set to -1.
-func (dsparams parentDSParams) FillParentSvcRetries(isLastCacheTier bool, atsMajorVer int, pasvc *ParentAbstractionService) []string {
+func (dsparams parentDSParams) FillParentSvcRetries(isLastCacheTier bool, atsMajorVersion uint, pasvc *ParentAbstractionService) []string {
 	warnings := []string{}
 
 	if !dsparams.HasRetryParams || // allow parentRetry to be empty, to simplify usage.
-		atsMajorVer < 6 { // ATS 5 and below don't support parent_retry directives
+		atsMajorVersion < 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")
 		pasvc.MaxSimpleRetries = -1
 		pasvc.MaxMarkdownRetries = -1
@@ -1210,13 +1217,13 @@ func (dsparams parentDSParams) FillParentSvcRetries(isLastCacheTier bool, atsMaj
 }
 
 // getSecondaryModeStr returns the secondary_mode string, and any warnings.
-func getSecondaryModeStr(tryAllPrimariesBeforeSecondary bool, atsMajorVer int, ds tc.DeliveryServiceName) (ParentAbstractionServiceParentSecondaryMode, []string) {
+func getSecondaryModeStr(tryAllPrimariesBeforeSecondary bool, atsMajorVersion uint, ds tc.DeliveryServiceName) (ParentAbstractionServiceParentSecondaryMode, []string) {
 	warnings := []string{}
 	if !tryAllPrimariesBeforeSecondary {
 		return ParentAbstractionServiceParentSecondaryModeDefault, warnings
 	}
-	if atsMajorVer < 8 {
-		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!")
+	if atsMajorVersion < 8 {
+		warnings = append(warnings, "DS '"+string(ds)+"' had Parameter "+ParentConfigRetryKeysDefault.SecondaryMode+" but this cache is "+strconv.FormatUint(uint64(atsMajorVersion), 10)+" and secondary_mode isn't supported in ATS until 8. Not using!")
 		return ParentAbstractionServiceParentSecondaryModeDefault, warnings
 	}
 
@@ -1527,7 +1534,7 @@ func getParentStrs(
 	ds *DeliveryService,
 	dsRequiredCapabilities map[int]map[ServerCapability]struct{},
 	parentInfos []parentInfo,
-	atsMajorVer int,
+	atsMajorVersion uint,
 	tryAllPrimariesBeforeSecondary bool,
 ) ([]*ParentAbstractionServiceParent, []*ParentAbstractionServiceParent, ParentAbstractionServiceParentSecondaryMode, []string) {
 	warnings := []string{}
@@ -1568,10 +1575,10 @@ func getParentStrs(
 	// secondaryParents := "" // "secparents" in Perl
 
 	// TODO the abstract->text needs to take this into account
-	// if atsMajorVer >= 6 && len(secondaryParentInfo) > 0 {
+	// if atsMajorVersion >= 6 && len(secondaryParentInfo) > 0 {
 	// parents = `parent="` + strings.Join(parentInfo, "") + `"`
 	// secondaryParents = ` secondary_parent="` + strings.Join(secondaryParentInfo, "") + `"`
-	secondaryMode, secondaryModeWarnings := getSecondaryModeStr(tryAllPrimariesBeforeSecondary, atsMajorVer, dsName)
+	secondaryMode, secondaryModeWarnings := getSecondaryModeStr(tryAllPrimariesBeforeSecondary, atsMajorVersion, dsName)
 	warnings = append(warnings, secondaryModeWarnings...)
 	// 	secondaryParents += secondaryModeStr
 	// } else {
@@ -1585,7 +1592,7 @@ func getParentStrs(
 func getMSOParentStrs(
 	ds *DeliveryService,
 	parentInfos []parentInfo,
-	atsMajorVer int,
+	atsMajorVersion uint,
 	msoAlgorithm ParentAbstractionServiceRetryPolicy,
 	tryAllPrimariesBeforeSecondary bool,
 ) ([]*ParentAbstractionServiceParent, []*ParentAbstractionServiceParent, ParentAbstractionServiceParentSecondaryMode, []string) {
@@ -1640,10 +1647,10 @@ func getMSOParentStrs(
 	// secondaryParents := ""
 
 	// TODO add this logic to the abstraction->text converter
-	// if atsMajorVer >= 6 && msoAlgorithm == "consistent_hash" && len(secondaryParentStr) > 0 {
+	// if atsMajorVersion >= 6 && msoAlgorithm == "consistent_hash" && len(secondaryParentStr) > 0 {
 	// parents = `parent="` + strings.Join(parentInfoTxt, "") + `"`
 	// secondaryParents = ` secondary_parent="` + secondaryParentStr + `"`
-	secondaryMode, secondaryModeWarnings := getSecondaryModeStr(tryAllPrimariesBeforeSecondary, atsMajorVer, dsName)
+	secondaryMode, secondaryModeWarnings := getSecondaryModeStr(tryAllPrimariesBeforeSecondary, atsMajorVersion, dsName)
 	warnings = append(warnings, secondaryModeWarnings...)
 	// 	secondaryParents += secondaryModeStr
 	// } else {
diff --git a/lib/go-atscfg/parentdotconfig_test.go b/lib/go-atscfg/parentdotconfig_test.go
index c5cb2a9cd4..9e09790a43 100644
--- a/lib/go-atscfg/parentdotconfig_test.go
+++ b/lib/go-atscfg/parentdotconfig_test.go
@@ -4437,6 +4437,251 @@ func TestMakeParentDotConfigFirstInnerLastTopology(t *testing.T) {
 	}
 }
 
+func TestMakeParentDotConfigOptVersion(t *testing.T) {
+	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.Topology = util.StrPtr("t0")
+	ds1.ProfileName = util.StrPtr("ds1Profile")
+	ds1.ProfileID = util.IntPtr(994)
+	ds1.MultiSiteOrigin = util.BoolPtr(true)
+
+	dses := []DeliveryService{*ds1}
+
+	parentConfigParams := []tc.Parameter{
+		tc.Parameter{
+			Name:       ParentConfigParamQStringHandling,
+			ConfigFile: "parent.config",
+			Value:      "myQStringHandlingParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
+			ConfigFile: "parent.config",
+			Value:      tc.AlgorithmConsistentHash,
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigParamQString,
+			ConfigFile: "parent.config",
+			Value:      "myQstringParam",
+			Profiles:   []byte(`["serverprofile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigRetryKeysDefault.Algorithm,
+			ConfigFile: "parent.config",
+			Value:      "consistent_hash",
+			Profiles:   []byte(`["ds1Profile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigRetryKeysDefault.ParentRetry,
+			ConfigFile: "parent.config",
+			Value:      "both",
+			Profiles:   []byte(`["ds1Profile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigRetryKeysDefault.MaxSimpleRetries,
+			ConfigFile: "parent.config",
+			Value:      "14",
+			Profiles:   []byte(`["ds1Profile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigRetryKeysDefault.MaxUnavailableRetries,
+			ConfigFile: "parent.config",
+			Value:      "9",
+			Profiles:   []byte(`["ds1Profile"]`),
+		},
+		tc.Parameter{
+			Name:       ParentConfigRetryKeysDefault.UnavailableRetryResponses,
+			ConfigFile: "parent.config",
+			Value:      `"400,503"`,
+			Profiles:   []byte(`["ds1Profile"]`),
+		},
+	}
+
+	server := makeTestParentServer()
+	server.Cachegroup = util.StrPtr("edgeCG")
+	server.CachegroupID = util.IntPtr(400)
+
+	origin0 := makeTestParentServer()
+	origin0.Cachegroup = util.StrPtr("originCG")
+	origin0.CachegroupID = util.IntPtr(500)
+	origin0.HostName = util.StrPtr("myorigin0")
+	origin0.ID = util.IntPtr(45)
+	setIP(origin0, "192.168.2.2")
+	origin0.Type = tc.OriginTypeName
+	origin0.TypeID = util.IntPtr(991)
+
+	origin1 := makeTestParentServer()
+	origin1.Cachegroup = util.StrPtr("originCG")
+	origin1.CachegroupID = util.IntPtr(500)
+	origin1.HostName = util.StrPtr("myorigin1")
+	origin1.ID = util.IntPtr(46)
+	setIP(origin1, "192.168.2.3")
+	origin1.Type = tc.OriginTypeName
+	origin1.TypeID = util.IntPtr(991)
+
+	servers := []Server{*server, *origin0, *origin1}
+
+	topologies := []tc.Topology{
+		tc.Topology{
+			Name: "t0",
+			Nodes: []tc.TopologyNode{
+				tc.TopologyNode{
+					Cachegroup: "edgeCG",
+					Parents:    []int{1},
+				},
+				tc.TopologyNode{
+					Cachegroup: "originCG",
+				},
+			},
+		},
+	}
+
+	serverCapabilities := map[int]map[ServerCapability]struct{}{}
+	dsRequiredCapabilities := map[int]map[ServerCapability]struct{}{}
+
+	eCG := &tc.CacheGroupNullable{}
+	eCG.Name = server.Cachegroup
+	eCG.ID = server.CachegroupID
+	eCG.ParentName = origin0.Cachegroup
+	eCG.ParentCachegroupID = origin0.CachegroupID
+	eCGType := tc.CacheGroupEdgeTypeName
+	eCG.Type = &eCGType
+
+	oCG := &tc.CacheGroupNullable{}
+	oCG.Name = origin0.Cachegroup
+	oCG.ID = origin0.CachegroupID
+	oCGType := tc.CacheGroupOriginTypeName
+	oCG.Type = &oCGType
+
+	cgs := []tc.CacheGroupNullable{*eCG, *oCG}
+
+	dss := []DeliveryServiceServer{
+		DeliveryServiceServer{
+			Server:          *origin0.ID,
+			DeliveryService: *ds1.ID,
+		},
+	}
+	cdn := &tc.CDN{
+		DomainName: "cdndomain.example",
+		Name:       "my-cdn-name",
+	}
+
+	// unavailable_server_retry_responses is not available as a feature in ATS 5, but is in ATS 9.
+
+	t.Run("Package Parameter 9 with no Opt ATSVersion has ATS 9 feature", func(t *testing.T) {
+		serverParams := []tc.Parameter{
+			tc.Parameter{
+				Name:       "trafficserver",
+				ConfigFile: "package",
+				Value:      "9",
+				Profiles:   []byte(`["global"]`),
+			},
+		}
+
+		opt := &ParentConfigOpts{AddComments: false, HdrComment: "myHeaderComment"}
+
+		cfg, err := MakeParentDotConfig(dses, server, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, opt.HdrComment)
+
+		if !strings.Contains(txt, `unavailable_server_retry_responses="400,503"`) {
+			t.Errorf(`expected Package Parameter ATS 9 with no Opt ATS Version to have unavailable_server_retry_responses feature', actual: '%v'`, txt)
+		}
+	})
+
+	t.Run("Package Parameter 5 with no Opt ATSVersion does not have the feature it shouldn't", func(t *testing.T) {
+		serverParams := []tc.Parameter{
+			tc.Parameter{
+				Name:       "trafficserver",
+				ConfigFile: "package",
+				Value:      "5",
+				Profiles:   []byte(`["global"]`),
+			},
+		}
+
+		opt := &ParentConfigOpts{AddComments: false, HdrComment: "myHeaderComment"}
+
+		cfg, err := MakeParentDotConfig(dses, server, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, opt.HdrComment)
+
+		if strings.Contains(txt, `unavailable_server_retry_responses`) {
+			t.Errorf(`expected Package Parameter ATS 5 with no Opt ATS Version to not have unavailable_server_retry_responses feature', actual: '%v'`, txt)
+		}
+	})
+
+	t.Run("Package Parameter 5 with Opt ATSVersion 9 uses Opt not Param.", func(t *testing.T) {
+		serverParams := []tc.Parameter{
+			tc.Parameter{
+				Name:       "trafficserver",
+				ConfigFile: "package",
+				Value:      "5",
+				Profiles:   []byte(`["global"]`),
+			},
+		}
+
+		opt := &ParentConfigOpts{
+			AddComments:     false,
+			HdrComment:      "myHeaderComment",
+			ATSMajorVersion: 9,
+		}
+
+		cfg, err := MakeParentDotConfig(dses, server, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, opt.HdrComment)
+
+		if !strings.Contains(txt, `unavailable_server_retry_responses`) {
+			t.Errorf(`expected Package Parameter ATS 5 with Opt ATS Version 9 to use Opt not Parameter with ATS 9 unavailable_server_retry_responses feature', actual: '%v'`, txt)
+		}
+	})
+
+	t.Run("Package Parameter 9 with Opt ATSVersion 5 uses Opt not Param.", func(t *testing.T) {
+		serverParams := []tc.Parameter{
+			tc.Parameter{
+				Name:       "trafficserver",
+				ConfigFile: "package",
+				Value:      "9",
+				Profiles:   []byte(`["global"]`),
+			},
+		}
+
+		opt := &ParentConfigOpts{
+			AddComments:     false,
+			HdrComment:      "myHeaderComment",
+			ATSMajorVersion: 5,
+		}
+
+		cfg, err := MakeParentDotConfig(dses, server, servers, topologies, serverParams, parentConfigParams, serverCapabilities, dsRequiredCapabilities, cgs, dss, cdn, opt)
+		if err != nil {
+			t.Fatal(err)
+		}
+		txt := cfg.Text
+
+		testComment(t, txt, opt.HdrComment)
+
+		if strings.Contains(txt, `unavailable_server_retry_responses`) {
+			t.Errorf(`expected Package Parameter ATS 9 with Opt ATS Version 5 to use Opt not Parameter with no ATS 9 unavailable_server_retry_responses feature', actual: '%v'`, txt)
+		}
+	})
+}
+
 func TestMakeParentDotConfigOriginIP(t *testing.T) {
 	hdr := &ParentConfigOpts{AddComments: false, HdrComment: "myHeaderComment"}
 
diff --git a/lib/go-atscfg/remapdotconfig.go b/lib/go-atscfg/remapdotconfig.go
index 4c82418894..31b02dc65d 100644
--- a/lib/go-atscfg/remapdotconfig.go
+++ b/lib/go-atscfg/remapdotconfig.go
@@ -56,6 +56,14 @@ type RemapDotConfigOpts struct {
 	// UseCoreStrategies is whether to use the ATS core strategies, rather than the parent_select plugin.
 	// This has no effect if UseStrategies is false.
 	UseStrategiesCore bool
+
+	// ATSMajorVersion is the integral major version of Apache Traffic server,
+	// used to generate the proper config for the proper version.
+	//
+	// If omitted or 0, the major version will be read from the Server's Profile Parameter config file 'package' name 'trafficserver'. If no such Parameter exists, the ATS version will default to 5.
+	// This was the old Traffic Control behavior, before the version was specifiable externally.
+	//
+	ATSMajorVersion uint
 }
 
 func MakeRemapDotConfig(
@@ -104,8 +112,8 @@ func MakeRemapDotConfig(
 		warnings = append(warnings, "making Delivery Service Cache Key Params, cache key will be missing! : "+err.Error())
 	}
 
-	atsMajorVersion, verWarns := getATSMajorVersion(serverParams)
-	warnings = append(warnings, verWarns...)
+	atsMajorVersion := getATSMajorVersion(opt.ATSMajorVersion, serverParams, &warnings)
+
 	serverPackageParamData, paramWarns := makeServerPackageParamData(server, serverParams)
 	warnings = append(warnings, paramWarns...)
 	cacheGroups, err := makeCGMap(cacheGroupArr)
@@ -246,7 +254,7 @@ func lastPrePostRemapLinesFor(dsConfigParamsMap map[string][]tc.Parameter, dsid
 
 // getServerConfigRemapDotConfigForMid returns the remap lines, any warnings, and any error.
 func getServerConfigRemapDotConfigForMid(
-	atsMajorVersion int,
+	atsMajorVersion uint,
 	profilesConfigParams map[int][]tc.Parameter,
 	dses []DeliveryService,
 	dsRegexes map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex,
@@ -389,7 +397,7 @@ func getServerConfigRemapDotConfigForEdge(
 	serverPackageParamData map[string]string, // map[paramName]paramVal for this server, config file 'package'
 	dses []DeliveryService,
 	dsRegexes map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex,
-	atsMajorVersion int,
+	atsMajorVersion uint,
 	header string,
 	server *Server,
 	nameTopologies map[TopologyName]tc.Topology,
@@ -497,7 +505,7 @@ type RemapLines struct {
 // The cacheKeyConfigParams map may be nil, if this ds profile had no cache key config params.
 // Returns the remap line, any warnings, and any error.
 func buildEdgeRemapLine(
-	atsMajorVersion int,
+	atsMajorVersion uint,
 	server *Server,
 	pData map[string]string,
 	text string,
@@ -739,7 +747,7 @@ func midHeaderRewriteConfigFileName(dsName string) string {
 }
 
 // getQStringIgnoreRemap returns the remap, whether cachekey was added.
-func getQStringIgnoreRemap(atsMajorVersion int) string {
+func getQStringIgnoreRemap(atsMajorVersion uint) string {
 	if atsMajorVersion < 7 {
 		log.Errorf("Unsupport version of ats found %v", atsMajorVersion)
 		return ""
diff --git a/lib/go-atscfg/strategiesdotconfig.go b/lib/go-atscfg/strategiesdotconfig.go
index b84b113bdf..c74b26bffa 100644
--- a/lib/go-atscfg/strategiesdotconfig.go
+++ b/lib/go-atscfg/strategiesdotconfig.go
@@ -41,6 +41,14 @@ type StrategiesYAMLOpts struct {
 	// This should be the text desired, without comment syntax (like # or //). The file's comment syntax will be added.
 	// To omit the header comment, pass the empty string.
 	HdrComment string
+
+	// ATSMajorVersion is the integral major version of Apache Traffic server,
+	// used to generate the proper config for the proper version.
+	//
+	// If omitted or 0, the major version will be read from the Server's Profile Parameter config file 'package' name 'trafficserver'. If no such Parameter exists, the ATS version will default to 5.
+	// This was the old Traffic Control behavior, before the version was specifiable externally.
+	//
+	ATSMajorVersion uint
 }
 
 func MakeStrategiesDotYAML(
@@ -57,10 +65,14 @@ func MakeStrategiesDotYAML(
 	cdn *tc.CDN,
 	opt *StrategiesYAMLOpts,
 ) (Cfg, error) {
+	warnings := []string{}
 	if opt == nil {
 		opt = &StrategiesYAMLOpts{}
 	}
-	parentAbstraction, warnings, err := makeParentDotConfigData(
+
+	atsMajorVersion := getATSMajorVersion(opt.ATSMajorVersion, tcServerParams, &warnings)
+
+	parentAbstraction, dataWarns, err := makeParentDotConfigData(
 		dses,
 		server,
 		servers,
@@ -73,18 +85,18 @@ func MakeStrategiesDotYAML(
 		dss,
 		cdn,
 		&ParentConfigOpts{
-			AddComments: opt.VerboseComments,
-			HdrComment:  opt.HdrComment,
+			AddComments:     opt.VerboseComments,
+			HdrComment:      opt.HdrComment,
+			ATSMajorVersion: opt.ATSMajorVersion,
 		}, // TODO change makeParentDotConfigData to its own opt?
+		atsMajorVersion,
 	)
+	warnings = append(warnings, dataWarns...)
 	if err != nil {
 		return Cfg{}, makeErr(warnings, err.Error())
 	}
 
-	atsMajorVer, verWarns := getATSMajorVersion(tcServerParams)
-	warnings = append(warnings, verWarns...)
-
-	text, paWarns, err := parentAbstractionToStrategiesDotYaml(parentAbstraction, opt, atsMajorVer)
+	text, paWarns, err := parentAbstractionToStrategiesDotYaml(parentAbstraction, opt, atsMajorVersion)
 	warnings = append(warnings, paWarns...)
 	if err != nil {
 		return Cfg{}, makeErr(warnings, err.Error())
@@ -107,7 +119,7 @@ func MakeStrategiesDotYAML(
 const YAMLDocumentStart = "---"
 const YAMLDocumentEnd = "..."
 
-func parentAbstractionToStrategiesDotYaml(pa *ParentAbstraction, opt *StrategiesYAMLOpts, atsMajorVersion int) (string, []string, error) {
+func parentAbstractionToStrategiesDotYaml(pa *ParentAbstraction, opt *StrategiesYAMLOpts, atsMajorVersion uint) (string, []string, error) {
 	warnings := []string{}
 	txt := YAMLDocumentStart +
 		getStrategyHostsSection(pa) +