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 efef336  Add TO Go remap.config (#3688)
efef336 is described below

commit efef3369bf5de61ae6dc4ca580ad2b77d331ade4
Author: Robert Butts <[email protected]>
AuthorDate: Tue Oct 1 18:41:19 2019 -0600

    Add TO Go remap.config (#3688)
    
    * Add TO Go remap.config
    
    * Add atscfg error log for multiple cacheurl/key
    
    Adds error logging whenever the config gen inserts multiple cacheurl
    or cachekey plugins on the same line.
    
    This isn't supported in ATS, and will usually do the wrong thing.
    But, we've always generated config like this, so for now, this
    doesn't change the config gen, but it logs the error
    
    * Add atstccfg (TO ATS config gen) remap.config
    
    * Fix godoc comments
    
    * Change sort.Sort(sort.StringSlice to sort.Strings
    
    * Fix atscfg ATS major version for >9.
    
    * Add GoDoc for util.HashInts
    
    * Change for loop to strings.Join
---
 CHANGELOG.md                                       |   3 +
 lib/go-atscfg/atscfg.go                            |  20 +-
 lib/go-atscfg/atscfg_test.go                       |  36 ++
 lib/go-atscfg/parentdotconfig.go                   |   4 -
 lib/go-atscfg/remapdotconfig.go                    | 409 +++++++++++++++
 lib/go-atscfg/remapdotconfig_test.go               | 129 +++++
 lib/go-tc/enum.go                                  |  18 +
 lib/go-util/num.go                                 |  18 +
 traffic_ops/ort/atstccfg/cachedotconfig.go         |   2 +-
 traffic_ops/ort/atstccfg/parentdotconfig.go        |   2 +-
 traffic_ops/ort/atstccfg/remapdotconfig.go         | 372 +++++++++++++
 traffic_ops/ort/atstccfg/routing.go                |   1 +
 traffic_ops/ort/atstccfg/toreq.go                  |  34 +-
 traffic_ops/ort/atstccfg/trafficops.go             |  17 +
 .../testing/api/v14/parentdotconfig_test.go        |  21 +-
 traffic_ops/testing/api/v14/remapdotconfig_test.go |  95 ++++
 traffic_ops/testing/api/v14/tc-fixtures.json       |   2 +-
 .../traffic_ops_golang/ats/{ => atsserver}/meta.go | 137 +----
 .../ats/{ => atsserver}/parentdotconfig.go         |   7 +-
 .../ats/atsserver/remapdotconfig.go                |  93 ++++
 traffic_ops/traffic_ops_golang/ats/config.go       |  23 -
 traffic_ops/traffic_ops_golang/ats/db.go           | 575 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/routing/routes.go   |   7 +-
 23 files changed, 1837 insertions(+), 188 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b1130b..a0900e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,9 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
   - /api/1.4/user/login/oauth `POST`
   - /api/1.1/profiles/:name/configfiles/ats/* `GET`
   - /api/1.1/dbdump `GET`
+  - /api/1.1/servers/:name/configfiles/ats/parent.config
+  - /api/1.1/servers/:name/configfiles/ats/remap.config
+
 - To support reusing a single riak cluster connection, an optional parameter 
is added to riak.conf: "HealthCheckInterval". This options takes a 'Duration' 
value (ie: 10s, 5m) which affects how often the riak cluster is health checked. 
 Default is currently set to: "HealthCheckInterval": "5s".
 - Added a new Go db/admin binary to replace the Perl db/admin.pl script which 
is now deprecated and will be removed in a future release. The new db/admin 
binary is essentially a drop-in replacement for db/admin.pl since it supports 
all of the same commands and options; therefore, it should be used in place of 
db/admin.pl for all the same tasks.
 - Added an API 1.4 endpoint, /api/1.4/cdns/dnsseckeys/refresh, to perform 
necessary behavior previously served outside the API under `/internal`.
diff --git a/lib/go-atscfg/atscfg.go b/lib/go-atscfg/atscfg.go
index 4209b8f..f2c52ed 100644
--- a/lib/go-atscfg/atscfg.go
+++ b/lib/go-atscfg/atscfg.go
@@ -28,6 +28,10 @@ import (
        "github.com/apache/trafficcontrol/lib/go-tc"
 )
 
+const InvalidID = -1
+
+const DefaultATSVersion = "5" // TODO Emulates Perl; change to 6? ATC no 
longer officially supports ATS 5.
+
 const HeaderCommentDateFormat = "Mon Jan 2 15:04:05 MST 2006"
 
 type ServerInfo struct {
@@ -36,6 +40,7 @@ type ServerInfo struct {
        CDNID                         int
        DomainName                    string
        HostName                      string
+       HTTPSPort                     int
        ID                            int
        IP                            string
        ParentCacheGroupID            int
@@ -67,16 +72,19 @@ func GenericHeaderComment(name string, toolName string, url 
string) string {
 
 // 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 not a number.
+// 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) {
-       if len(atsVersion) == 0 {
-               return 0, errors.New("ats version missing")
+       dotPos := strings.Index(atsVersion, ".")
+       if dotPos == -1 {
+               dotPos = len(atsVersion) // if there's no '.' then assume the 
whole string is just a major version.
        }
-       atsMajorVer, err := strconv.Atoi(atsVersion[:1])
+       majorVerStr := atsVersion[:dotPos]
+
+       majorVer, err := strconv.ParseUint(majorVerStr, 10, 64)
        if err != nil {
-               return 0, errors.New("ats version parameter '" + atsVersion + 
"' is not a number")
+               return 0, errors.New("unexpected version format, expected e.g. 
'7.1.2.whatever'")
        }
-       return atsMajorVer, nil
+       return int(majorVer), nil
 }
 
 type DeliveryServiceID int
diff --git a/lib/go-atscfg/atscfg_test.go b/lib/go-atscfg/atscfg_test.go
index f81b053..27fd9ff 100644
--- a/lib/go-atscfg/atscfg_test.go
+++ b/lib/go-atscfg/atscfg_test.go
@@ -80,3 +80,39 @@ func TestTrimParamUnderscoreNumSuffix(t *testing.T) {
                }
        }
 }
+
+func TestGetATSMajorVersionFromATSVersion(t *testing.T) {
+       inputExpected := map[string]int{
+               `7.1.2-34.56abcde.el7.centos.x86_64`:    7,
+               `8`:                                     8,
+               `8.1`:                                   8,
+               `10.1`:                                  10,
+               `1234.1.2-34.56abcde.el7.centos.x86_64`: 1234,
+       }
+       errExpected := []string{
+               "a7.1.2-34.56abcde.el7.centos.x86_64",
+               `-7.1.2-34.56abcde.el7.centos.x86_64`,
+               ".7.1.2-34.56abcde.el7.centos.x86_64",
+               "7a.1.2-34.56abcde.el7.centos.x86_64",
+               "7-a.1.2-34.56abcde.el7.centos.x86_64",
+               "7-2.1.2-34.56abcde.el7.centos.x86_64",
+               "100-2.1.2-34.56abcde.el7.centos.x86_64",
+               "7a",
+               "",
+               "-",
+               ".",
+       }
+
+       for input, expected := range inputExpected {
+               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 {
+                       t.Errorf("input %v expected: error, actual: nil error 
'%v'", input, actual)
+               }
+       }
+}
diff --git a/lib/go-atscfg/parentdotconfig.go b/lib/go-atscfg/parentdotconfig.go
index e39b4b1..17a01ea 100644
--- a/lib/go-atscfg/parentdotconfig.go
+++ b/lib/go-atscfg/parentdotconfig.go
@@ -31,8 +31,6 @@ import (
        "github.com/apache/trafficcontrol/lib/go-util"
 )
 
-const InvalidID = -1
-
 const ParentConfigParamQStringHandling = "psel.qstring_handling"
 const ParentConfigParamMSOAlgorithm = "mso.algorithm"
 const ParentConfigParamMSOParentRetry = "mso.parent_retry"
@@ -57,8 +55,6 @@ const ParentConfigCacheParamNotAParent = "not_a_parent"
 // TODO change, this is terrible practice, using a hard-coded key. What if 
there were a delivery service named "all_parents" (transliterated Perl)
 const DeliveryServicesAllParentsKey = "all_parents"
 
-const DefaultATSVersion = "5" // TODO Emulates Perl; change to 6? ATC no 
longer officially supports ATS 5.
-
 type ParentConfigDS struct {
        Name            tc.DeliveryServiceName
        QStringIgnore   tc.QStringIgnore
diff --git a/lib/go-atscfg/remapdotconfig.go b/lib/go-atscfg/remapdotconfig.go
new file mode 100644
index 0000000..2ac8d04
--- /dev/null
+++ b/lib/go-atscfg/remapdotconfig.go
@@ -0,0 +1,409 @@
+package atscfg
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "errors"
+       "sort"
+       "strconv"
+       "strings"
+
+       "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+const CacheURLParameterConfigFile = "cacheurl.config"
+const CacheKeyParameterConfigFile = "cachekey.config"
+
+type RemapConfigDSData struct {
+       ID                       int
+       Type                     tc.DSType
+       OriginFQDN               *string
+       MidHeaderRewrite         *string
+       CacheURL                 *string
+       RangeRequestHandling     *int
+       CacheKeyConfigParams     map[string]string
+       RemapText                *string
+       EdgeHeaderRewrite        *string
+       SigningAlgorithm         *string
+       Name                     string
+       QStringIgnore            *int
+       RegexRemap               *string
+       FQPacingRate             *int
+       DSCP                     int
+       RoutingName              *string
+       MultiSiteOrigin          *string
+       Pattern                  *string
+       RegexType                *string
+       Domain                   *string
+       RegexSetNumber           *string
+       OriginShield             *string
+       ProfileID                *int
+       Protocol                 *int
+       AnonymousBlockingEnabled *bool
+       Active                   bool
+}
+
+func MakeRemapDotConfig(
+       serverName tc.CacheName,
+       toToolName string, // tm.toolname global parameter (TODO: cache itself?)
+       toURL string, // tm.url global parameter (TODO: cache itself?)
+       atsMajorVersion int,
+       cacheURLConfigParams map[string]string, // map[name]value for this 
server's profile and config file 'cacheurl.config'
+       dsProfilesCacheKeyConfigParams map[int]map[string]string, // 
map[profileID][paramName]paramVal for this server's profile, config file 
'cachekey.config'
+       serverPackageParamData map[string]string, // map[paramName]paramVal for 
this server, config file 'package'
+       serverInfo *ServerInfo, // ServerInfo for this server
+       remapDSData []RemapConfigDSData,
+) string {
+       hdr := GenericHeaderComment(string(serverName), toToolName, toURL)
+       text := ""
+       if tc.CacheTypeFromString(serverInfo.Type) == tc.CacheTypeMid {
+               text = GetServerConfigRemapDotConfigForMid(atsMajorVersion, 
dsProfilesCacheKeyConfigParams, serverInfo, remapDSData, hdr)
+       } else {
+               text = 
GetServerConfigRemapDotConfigForEdge(cacheURLConfigParams, 
dsProfilesCacheKeyConfigParams, serverPackageParamData, serverInfo, 
remapDSData, atsMajorVersion, hdr)
+       }
+       return text
+}
+
+func GetServerConfigRemapDotConfigForMid(
+       atsMajorVersion int,
+       profilesCacheKeyConfigParams map[int]map[string]string,
+       server *ServerInfo,
+       dses []RemapConfigDSData,
+       header string,
+) string {
+       midRemaps := map[string]string{}
+       for _, ds := range dses {
+               if ds.Type.IsLive() && !ds.Type.IsNational() {
+                       continue // Live local delivery services skip mids
+               }
+
+               if ds.OriginFQDN == nil || *ds.OriginFQDN == "" {
+                       log.Warnf("GetServerConfigRemapDotConfigForMid ds '" + 
ds.Name + "' has no origin fqdn, skipping!") // TODO confirm - Perl uses 
without checking!
+                       continue
+               }
+
+               if midRemaps[*ds.OriginFQDN] != "" {
+                       continue // skip remap rules from extra HOST_REGEXP 
entries
+               }
+
+               // multiple uses of cacheurl and cachekey plugins don't work 
right in ATS, but Perl has always done it.
+               // So for now, keep track of it, so we can log an error when it 
happens.
+               hasCacheURL := false
+               hasCacheKey := false
+
+               midRemap := ""
+               if ds.MidHeaderRewrite != nil && *ds.MidHeaderRewrite != "" {
+                       midRemap += ` @plugin=header_rewrite.so @pparam=` + 
MidHeaderRewriteConfigFileName(ds.Name)
+               }
+               if ds.QStringIgnore != nil && *ds.QStringIgnore == 
tc.QueryStringIgnoreIgnoreInCacheKeyAndPassUp {
+                       qstr, addedCacheURL, addedCacheKey := 
GetQStringIgnoreRemap(atsMajorVersion)
+                       if addedCacheURL {
+                               hasCacheURL = true
+                       }
+                       if addedCacheKey {
+                               hasCacheKey = true
+                       }
+                       midRemap += qstr
+               }
+               if ds.CacheURL != nil && *ds.CacheURL != "" {
+                       if hasCacheURL {
+                               log.Errorln("DeliveryService qstring_ignore and 
cacheurl both add cacheurl, but ATS cacheurl doesn't work correctly with 
multiple entries! Adding anyway!")
+                       }
+                       midRemap += ` @plugin=cacheurl.so @pparam=` + 
CacheURLConfigFileName(ds.Name)
+               }
+
+               if ds.ProfileID != nil && 
len(profilesCacheKeyConfigParams[*ds.ProfileID]) > 0 {
+                       if hasCacheKey {
+                               log.Errorln("DeliveryService qstring_ignore and 
cachekey params both add cachekey, but ATS cachekey doesn't work correctly with 
multiple entries! Adding anyway!")
+                       }
+                       midRemap += ` @plugin=cachekey.so`
+                       for name, val := range 
profilesCacheKeyConfigParams[*ds.ProfileID] {
+                               midRemap += ` @pparam=--` + name + "=" + val
+                       }
+               }
+               if ds.RangeRequestHandling != nil && *ds.RangeRequestHandling 
== tc.RangeRequestHandlingCacheRangeRequest {
+                       midRemap += ` @plugin=cache_range_requests.so`
+               }
+
+               if midRemap != "" {
+                       midRemaps[*ds.OriginFQDN] = midRemap
+               }
+       }
+
+       textLines := []string{}
+       for originFQDN, midRemap := range midRemaps {
+               textLines = append(textLines, "map "+originFQDN+" 
"+originFQDN+midRemap+"\n")
+       }
+       sort.Strings(textLines)
+
+       text := header
+       text += strings.Join(textLines, "")
+       return text
+}
+
+func GetServerConfigRemapDotConfigForEdge(
+       cacheURLConfigParams map[string]string,
+       profilesCacheKeyConfigParams map[int]map[string]string,
+       serverPackageParamData map[string]string, // map[paramName]paramVal for 
this server, config file 'package'
+       server *ServerInfo,
+       dses []RemapConfigDSData,
+       atsMajorVersion int,
+       header string,
+) string {
+       for name, val := range serverPackageParamData {
+               serverPackageParamData[name] = strings.Replace(val, 
"__HOSTNAME__", server.HostName+"."+server.DomainName, -1)
+       }
+
+       textLines := []string{}
+
+       for _, ds := range dses {
+               remapText := ""
+               if ds.Type == tc.DSTypeAnyMap {
+                       if ds.RemapText == nil {
+                               log.Errorln("ds '" + ds.Name + "' is ANY_MAP, 
but has no remap text - skipping")
+                               continue
+                       }
+                       remapText = *ds.RemapText + "\n"
+                       textLines = append(textLines, remapText)
+                       continue
+               }
+
+               remapLines, err := MakeEdgeDSDataRemapLines(ds, server)
+               if err != nil {
+                       log.Errorln("making remap lines for DS '" + ds.Name + 
"' - skipping! : " + err.Error())
+                       continue
+               }
+
+               for _, line := range remapLines {
+                       profilecacheKeyConfigParams := (map[string]string)(nil)
+                       if ds.ProfileID != nil {
+                               profilecacheKeyConfigParams = 
profilesCacheKeyConfigParams[*ds.ProfileID]
+                       }
+                       remapText = BuildRemapLine(cacheURLConfigParams, 
atsMajorVersion, server, serverPackageParamData, remapText, ds, line.From, 
line.To, profilecacheKeyConfigParams)
+               }
+               textLines = append(textLines, remapText)
+       }
+
+       text := header
+       sort.Strings(textLines)
+       text += strings.Join(textLines, "")
+       return text
+}
+
+// BuildRemapLine builds the remap line for the given server and delivery 
service.
+// The cacheKeyConfigParams map may be nil, if this ds profile had no cache 
key config params.
+func BuildRemapLine(cacheURLConfigParams map[string]string, atsMajorVersion 
int, server *ServerInfo, pData map[string]string, text string, ds 
RemapConfigDSData, mapFrom string, mapTo string, cacheKeyConfigParams 
map[string]string) string {
+       // ds = 'remap' in perl
+
+       mapFrom = strings.Replace(mapFrom, `__http__`, server.HostName, -1)
+
+       if _, hasDSCPRemap := pData["dscp_remap"]; hasDSCPRemap {
+               text += "map    " + mapFrom + "     " + mapTo + ` 
@plugin=dscp_remap.so @pparam=` + strconv.Itoa(ds.DSCP)
+       } else {
+               text += "map    " + mapFrom + "     " + mapTo + ` 
@plugin=header_rewrite.so @pparam=dscp/set_dscp_` + strconv.Itoa(ds.DSCP) + 
".config"
+       }
+
+       if ds.EdgeHeaderRewrite != nil && *ds.EdgeHeaderRewrite != "" {
+               text += ` @plugin=header_rewrite.so @pparam=` + 
EdgeHeaderRewriteConfigFileName(ds.Name)
+       }
+
+       if ds.SigningAlgorithm != nil && *ds.SigningAlgorithm != "" {
+               if *ds.SigningAlgorithm == tc.SigningAlgorithmURLSig {
+                       text += ` @plugin=url_sig.so @pparam=url_sig_` + 
ds.Name + ".config"
+               } else if *ds.SigningAlgorithm == tc.SigningAlgorithmURISigning 
{
+                       text += ` @plugin=uri_signing.so @pparam=uri_signing_` 
+ ds.Name + ".config"
+               }
+       }
+
+       // multiple uses of cacheurl and cachekey plugins don't work right in 
ATS, but Perl has always done it.
+       // So for now, keep track of it, so we can log an error when it happens.
+       hasCacheURL := false
+       hasCacheKey := false
+
+       if ds.QStringIgnore != nil {
+               if *ds.QStringIgnore == tc.QueryStringIgnoreDropAtEdge {
+                       dqsFile := "drop_qstring.config"
+                       text += ` @plugin=regex_remap.so @pparam=` + dqsFile
+               } else if *ds.QStringIgnore == 
tc.QueryStringIgnoreIgnoreInCacheKeyAndPassUp {
+                       if _, globalExists := cacheURLConfigParams["location"]; 
globalExists {
+                               log.Warnln("qstring_ignore == 1, but global 
cacheurl.config param exists, so skipping remap rename 
config_file=cacheurl.config parameter")
+                       } else {
+                               qstr, addedCacheURL, addedCacheKey := 
GetQStringIgnoreRemap(atsMajorVersion)
+                               if addedCacheURL {
+                                       hasCacheURL = true
+                               }
+                               if addedCacheKey {
+                                       hasCacheKey = true
+                               }
+                               text += qstr
+                       }
+               }
+       }
+
+       if ds.CacheURL != nil && *ds.CacheURL != "" {
+               if hasCacheURL {
+                       log.Errorln("DeliveryService qstring_ignore and 
cacheurl both add cacheurl, but ATS cacheurl doesn't work correctly with 
multiple entries! Adding anyway!")
+               }
+               text += ` @plugin=cacheurl.so @pparam=` + 
CacheURLConfigFileName(ds.Name)
+       }
+
+       if len(cacheKeyConfigParams) > 0 {
+               if hasCacheKey {
+                       log.Errorln("DeliveryService qstring_ignore and params 
both add cachekey, but ATS cachekey doesn't work correctly with multiple 
entries! Adding anyway!")
+               }
+               text += ` @plugin=cachekey.so`
+
+               keys := []string{}
+               for key, _ := range cacheKeyConfigParams {
+                       keys = append(keys, key)
+               }
+               sort.Sort(sort.StringSlice(keys))
+
+               for _, key := range keys {
+                       text += ` @pparam=--` + key + "=" + 
cacheKeyConfigParams[key]
+               }
+       }
+
+       // Note: should use full path here?
+       if ds.RegexRemap != nil && *ds.RegexRemap != "" {
+               text += ` @plugin=regex_remap.so @pparam=regex_remap_` + 
ds.Name + ".config"
+       }
+       if ds.RangeRequestHandling != nil {
+               if *ds.RangeRequestHandling == 
tc.RangeRequestHandlingBackgroundFetch {
+                       text += ` @plugin=background_fetch.so 
@pparam=bg_fetch.config`
+               } else if *ds.RangeRequestHandling == 
tc.RangeRequestHandlingCacheRangeRequest {
+                       text += ` @plugin=cache_range_requests.so `
+               }
+       }
+       if ds.RemapText != nil && *ds.RemapText != "" {
+               text += " " + *ds.RemapText
+       }
+
+       if ds.FQPacingRate != nil && *ds.FQPacingRate > 0 {
+               text += ` @plugin=fq_pacing.so @pparam=--rate=` + 
strconv.Itoa(*ds.FQPacingRate)
+       }
+       text += "\n"
+       return text
+}
+
+func DSProfileIDs(dses []RemapConfigDSData) []int {
+       dsProfileIDs := []int{}
+       for _, ds := range dses {
+               if ds.ProfileID != nil {
+                       // TODO determine if this is right, if the DS has no 
profile
+                       dsProfileIDs = append(dsProfileIDs, *ds.ProfileID)
+               }
+       }
+       return dsProfileIDs
+}
+
+type RemapLine struct {
+       From string
+       To   string
+}
+
+// MakeEdgeDSDataRemapLines returns the remap lines for the given server and 
delivery service.
+// Returns nil, if the given server and ds have no remap lines, i.e. the DS 
match is not a host regex, or has no origin FQDN.
+func MakeEdgeDSDataRemapLines(ds RemapConfigDSData, server *ServerInfo) 
([]RemapLine, error) {
+       if ds.RegexType == nil || tc.DSMatchType(*ds.RegexType) != 
tc.DSMatchTypeHostRegex || ds.OriginFQDN == nil || *ds.OriginFQDN == "" {
+               return nil, nil
+       }
+
+       if ds.OriginFQDN == nil {
+               return nil, errors.New("ds missing origin fqdn")
+       }
+       if ds.Pattern == nil {
+               return nil, errors.New("ds missing regex pattern")
+       }
+       if ds.Protocol == nil {
+               return nil, errors.New("ds missing protocol")
+       }
+       if ds.Domain == nil {
+               return nil, errors.New("ds missing domain")
+       }
+
+       remapLines := []RemapLine{}
+       hostRegex := *ds.Pattern
+       mapTo := *ds.OriginFQDN + "/"
+
+       mapFromHTTP := "http://"; + hostRegex + "/"
+       mapFromHTTPS := "https://"; + hostRegex + "/"
+       if strings.HasSuffix(hostRegex, `.*`) {
+               re := hostRegex
+               re = strings.Replace(re, `\`, ``, -1)
+               re = strings.Replace(re, `.*`, ``, -1)
+
+               hName := "__http__"
+               if ds.Type.IsDNS() {
+                       if ds.RoutingName == nil {
+                               return nil, errors.New("ds is dns, but missing 
routing name")
+                       }
+                       hName = *ds.RoutingName
+               }
+
+               portStr := ""
+               if hName == "__http__" && server.Port > 0 && server.Port != 80 {
+                       portStr = ":" + strconv.Itoa(server.Port)
+               }
+
+               httpsPortStr := ""
+               if hName == "__http__" && server.HTTPSPort > 0 && 
server.HTTPSPort != 443 {
+                       httpsPortStr = ":" + strconv.Itoa(server.HTTPSPort)
+               }
+
+               mapFromHTTP = "http://"; + hName + re + *ds.Domain + portStr + 
"/"
+               mapFromHTTPS = "https://"; + hName + re + *ds.Domain + 
httpsPortStr + "/"
+       }
+
+       if *ds.Protocol == tc.DSProtocolHTTP || *ds.Protocol == 
tc.DSProtocolHTTPAndHTTPS {
+               remapLines = append(remapLines, RemapLine{From: mapFromHTTP, 
To: mapTo})
+       }
+       if *ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == 
tc.DSProtocolHTTPToHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS {
+               remapLines = append(remapLines, RemapLine{From: mapFromHTTPS, 
To: mapTo})
+       }
+
+       return remapLines, nil
+}
+
+func EdgeHeaderRewriteConfigFileName(dsName string) string {
+       return "hdr_rw_" + dsName + ".config"
+}
+
+func MidHeaderRewriteConfigFileName(dsName string) string {
+       return "hdr_rw_mid_" + dsName + ".config"
+}
+
+func CacheURLConfigFileName(dsName string) string {
+       return "cacheurl_" + dsName + ".config"
+}
+
+// GetQStringIgnoreRemap returns the remap, whether cacheurl was added, and 
whether cachekey was added.
+func GetQStringIgnoreRemap(atsMajorVersion int) (string, bool, bool) {
+       if atsMajorVersion >= 6 {
+               addingCacheURL := false
+               addingCacheKey := true
+               return ` @plugin=cachekey.so @pparam=--separator= 
@pparam=--remove-all-params=true @pparam=--remove-path=true 
@pparam=--capture-prefix-uri=/^([^?]*)/$1/`, addingCacheURL, addingCacheKey
+       } else {
+               addingCacheURL := true
+               addingCacheKey := false
+               return ` @plugin=cacheurl.so @pparam=cacheurl_qstring.config`, 
addingCacheURL, addingCacheKey
+       }
+}
diff --git a/lib/go-atscfg/remapdotconfig_test.go 
b/lib/go-atscfg/remapdotconfig_test.go
new file mode 100644
index 0000000..94a1933
--- /dev/null
+++ b/lib/go-atscfg/remapdotconfig_test.go
@@ -0,0 +1,129 @@
+package atscfg
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "strings"
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
+)
+
+func TestMakeRemapDotConfig(t *testing.T) {
+       serverName := tc.CacheName("server0")
+       toToolName := "to0"
+       toURL := "trafficops.example.net"
+       atsMajorVersion := 7
+
+       cacheURLConfigParams := map[string]string{
+               "not_location": "notinconfig",
+       }
+
+       dsProfilesCacheKeyConfigParams := map[int]map[string]string{
+               46: map[string]string{
+                       "cachekeykey": "cachekeyval",
+               },
+       }
+
+       serverPackageParamData := map[string]string{
+               "serverpkgval": "serverpkgval __HOSTNAME__ foo",
+       }
+
+       serverInfo := &ServerInfo{
+               CacheGroupID:                  42,
+               CDN:                           "mycdn",
+               CDNID:                         43,
+               DomainName:                    "mydomain",
+               HostName:                      "myhost",
+               HTTPSPort:                     12443,
+               ID:                            44,
+               IP:                            "192.168.2.4",
+               ParentCacheGroupID:            45,
+               ParentCacheGroupType:          "CGType4",
+               ProfileID:                     46,
+               ProfileName:                   "MyProfile",
+               Port:                          12080,
+               SecondaryParentCacheGroupID:   47,
+               SecondaryParentCacheGroupType: "MySecondaryParentCG",
+               Type:                          "EDGE",
+       }
+
+       remapDSData := []RemapConfigDSData{
+               RemapConfigDSData{
+                       ID:                       48,
+                       Type:                     "HTTP_LIVE",
+                       OriginFQDN:               
util.StrPtr("origin.example.test"),
+                       MidHeaderRewrite:         util.StrPtr("mymidrewrite"),
+                       CacheURL:                 util.StrPtr("mycacheurl"),
+                       RangeRequestHandling:     util.IntPtr(0),
+                       CacheKeyConfigParams:     
map[string]string{"cachekeyparamname": "cachekeyparamval"},
+                       RemapText:                util.StrPtr("myremaptext"),
+                       EdgeHeaderRewrite:        
util.StrPtr("myedgeheaderrewrite"),
+                       SigningAlgorithm:         util.StrPtr("url_sig"),
+                       Name:                     "mydsname",
+                       QStringIgnore:            util.IntPtr(0),
+                       RegexRemap:               util.StrPtr("myregexremap"),
+                       FQPacingRate:             util.IntPtr(0),
+                       DSCP:                     0,
+                       RoutingName:              util.StrPtr("myroutingname"),
+                       MultiSiteOrigin:          util.StrPtr("mymso"),
+                       Pattern:                  util.StrPtr("myregexpattern"),
+                       RegexType:                
util.StrPtr(string(tc.DSMatchTypeHostRegex)),
+                       Domain:                   util.StrPtr("mydomain"),
+                       RegexSetNumber:           util.StrPtr("myregexsetnum"),
+                       OriginShield:             util.StrPtr("myoriginshield"),
+                       ProfileID:                util.IntPtr(49),
+                       Protocol:                 util.IntPtr(0),
+                       AnonymousBlockingEnabled: util.BoolPtr(false),
+                       Active:                   true,
+               },
+       }
+
+       txt := MakeRemapDotConfig(serverName, toToolName, toURL, 
atsMajorVersion, cacheURLConfigParams, dsProfilesCacheKeyConfigParams, 
serverPackageParamData, serverInfo, remapDSData)
+
+       txt = strings.TrimSpace(txt)
+
+       testComment(t, txt, string(serverName), toToolName, toURL)
+
+       txtLines := strings.Split(txt, "\n")
+
+       if len(txtLines) != 2 {
+               t.Errorf("expected one line for each remap plus a comment, 
actual: '%v' count %v", txt, len(txtLines))
+       }
+
+       remapLine := txtLines[1]
+
+       if !strings.HasPrefix(remapLine, "map") {
+               t.Errorf("expected to start with 'map', actual '%v'", txt)
+       }
+
+       if !strings.Contains(remapLine, "http://myregexpattern";) {
+               t.Errorf("expected to contain routing name, actual '%v'", txt)
+       }
+
+       if !strings.Contains(remapLine, "http://myregexpattern";) {
+               t.Errorf("expected to contain routing name, actual '%v'", txt)
+       }
+
+       if !strings.Contains(remapLine, "origin.example.test") {
+               t.Errorf("expected to contain origin FQDN, actual '%v'", txt)
+       }
+}
diff --git a/lib/go-tc/enum.go b/lib/go-tc/enum.go
index 44c1a00..4020a89 100644
--- a/lib/go-tc/enum.go
+++ b/lib/go-tc/enum.go
@@ -55,6 +55,8 @@ type DeliveryServiceName string
 // CacheType is the type (or tier) of a CDN cache.
 type CacheType string
 
+const OriginLocationType = "ORG_LOC"
+
 const (
        // CacheTypeEdge represents an edge cache.
        CacheTypeEdge = CacheType("EDGE")
@@ -112,6 +114,16 @@ func CacheTypeFromString(s string) CacheType {
        return CacheTypeInvalid
 }
 
+// These are prefixed "QueryStringIgnore" even though the values don't always 
indicate ignoring, because the database column is named "qstring_ignore"
+
+const QueryStringIgnoreUseInCacheKeyAndPassUp = 0
+const QueryStringIgnoreIgnoreInCacheKeyAndPassUp = 1
+const QueryStringIgnoreDropAtEdge = 2
+
+const RangeRequestHandlingDontCache = 0
+const RangeRequestHandlingBackgroundFetch = 1
+const RangeRequestHandlingCacheRangeRequest = 2
+
 // DSTypeCategory is the Delivery Service type category: HTTP or DNS
 type DSTypeCategory string
 
@@ -150,6 +162,12 @@ func DSTypeCategoryFromString(s string) DSTypeCategory {
 }
 
 const SigningAlgorithmURLSig = "url_sig"
+const SigningAlgorithmURISigning = "uri_signing"
+
+const DSProtocolHTTP = 0
+const DSProtocolHTTPS = 1
+const DSProtocolHTTPAndHTTPS = 2
+const DSProtocolHTTPToHTTPS = 3
 
 // CacheStatus represents the Traffic Server status set in Traffic Ops 
(online, offline, admin_down, reported). The string values of this type should 
match the Traffic Ops values.
 type CacheStatus string
diff --git a/lib/go-util/num.go b/lib/go-util/num.go
index 32ccd47..bcbd313 100644
--- a/lib/go-util/num.go
+++ b/lib/go-util/num.go
@@ -20,6 +20,9 @@ package util
  */
 
 import (
+       "crypto/sha512"
+       "encoding/base64"
+       "encoding/binary"
        "errors"
        "strconv"
 )
@@ -106,3 +109,18 @@ func BytesLenSplit(s []byte, n int) [][]byte {
        }
        return ss
 }
+
+// HashInts returns a string hash of the given ints.
+func HashInts(ints []int) string {
+       if len(ints) == 0 {
+               return ""
+       }
+       hsh := sha512.New()
+       buf := make([]byte, binary.MaxVarintLen64)
+       for _, i := range ints {
+               wroteLen := binary.PutVarint(buf, int64(i))
+               hsh.Write(buf[:wroteLen])
+       }
+       bts := hsh.Sum(nil)
+       return base64.RawURLEncoding.EncodeToString(bts)
+}
diff --git a/traffic_ops/ort/atstccfg/cachedotconfig.go 
b/traffic_ops/ort/atstccfg/cachedotconfig.go
index 87e6554..8c3e772 100644
--- a/traffic_ops/ort/atstccfg/cachedotconfig.go
+++ b/traffic_ops/ort/atstccfg/cachedotconfig.go
@@ -54,7 +54,7 @@ func GetConfigFileProfileCacheDotConfig(cfg TCCfg, 
profileNameOrID string) (stri
                profileServerIDsMap[sv.ID] = struct{}{}
        }
 
-       dsServers, err := GetDeliveryServiceServers(cfg, profileServerIDs)
+       dsServers, err := GetDeliveryServiceServers(cfg, nil, profileServerIDs)
        if err != nil {
                return "", errors.New("getting parent.config cachegroup parent 
server delivery service servers: " + err.Error())
        }
diff --git a/traffic_ops/ort/atstccfg/parentdotconfig.go 
b/traffic_ops/ort/atstccfg/parentdotconfig.go
index b44a50a..04e985e 100644
--- a/traffic_ops/ort/atstccfg/parentdotconfig.go
+++ b/traffic_ops/ort/atstccfg/parentdotconfig.go
@@ -200,7 +200,7 @@ func GetConfigFileServerParentDotConfig(cfg TCCfg, 
serverNameOrID string) (strin
        }
        cgServerIDs = append(cgServerIDs, server.ID)
 
-       cgDSServers, err := GetDeliveryServiceServers(cfg, cgServerIDs)
+       cgDSServers, err := GetDeliveryServiceServers(cfg, nil, cgServerIDs)
        if err != nil {
                return "", errors.New("getting parent.config cachegroup parent 
server delivery service servers: " + err.Error())
        }
diff --git a/traffic_ops/ort/atstccfg/remapdotconfig.go 
b/traffic_ops/ort/atstccfg/remapdotconfig.go
new file mode 100755
index 0000000..d0809d2
--- /dev/null
+++ b/traffic_ops/ort/atstccfg/remapdotconfig.go
@@ -0,0 +1,372 @@
+package main
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "errors"
+       "sort"
+       "strconv"
+       "strings"
+
+       "github.com/apache/trafficcontrol/lib/go-atscfg"
+       "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
+)
+
+func GetConfigFileServerRemapDotConfig(cfg TCCfg, serverNameOrID string) 
(string, error) {
+       // TODO TOAPI add /servers?cdn=1 query param
+       servers, err := GetServers(cfg)
+       if err != nil {
+               return "", errors.New("getting servers: " + err.Error())
+       }
+
+       server := tc.Server{ID: atscfg.InvalidID}
+       if serverID, err := strconv.Atoi(serverNameOrID); err == nil {
+               for _, toServer := range servers {
+                       if toServer.ID == serverID {
+                               server = toServer
+                               break
+                       }
+               }
+       } else {
+               serverName := serverNameOrID
+               for _, toServer := range servers {
+                       if toServer.HostName == serverName {
+                               server = toServer
+                               break
+                       }
+               }
+       }
+       if server.ID == atscfg.InvalidID {
+               return "", errors.New("server '" + serverNameOrID + " not found 
in servers")
+       }
+
+       serverName := server.HostName
+
+       cdn, err := GetCDN(cfg, tc.CDNName(server.CDNName))
+       if err != nil {
+               return "", errors.New("getting cdn '" + string(server.CDNName) 
+ "': " + err.Error())
+       }
+
+       serverCDNDomain := cdn.DomainName
+
+       toToolName, toURL, err := GetTOToolNameAndURLFromTO(cfg)
+       if err != nil {
+               return "", errors.New("getting global parameters: " + 
err.Error())
+       }
+
+       serverProfileParameters, err := GetServerProfileParameters(cfg, 
server.Profile)
+       if err != nil {
+               return "", errors.New("getting server profile '" + 
server.Profile + "' parameters: " + err.Error())
+       }
+
+       atsVersionParam := ""
+       for _, param := range serverProfileParameters {
+               if param.ConfigFile != "package" || param.Name != 
"trafficserver" {
+                       continue
+               }
+               atsVersionParam = param.Value
+               break
+       }
+       if atsVersionParam == "" {
+               atsVersionParam = atscfg.DefaultATSVersion
+       }
+
+       atsMajorVer, err := 
atscfg.GetATSMajorVersionFromATSVersion(atsVersionParam)
+       if err != nil {
+               return "", errors.New("getting ATS major version from version 
parameter (profile '" + server.Profile + "' configFile 'package' name 
'trafficserver'): " + err.Error())
+       }
+
+       deliveryServices, err := GetCDNDeliveryServices(cfg, server.CDNID)
+       if err != nil {
+               return "", errors.New("getting delivery services: " + 
err.Error())
+       }
+
+       dsIDs := []int{}
+       for _, ds := range deliveryServices {
+               if ds.ID == nil {
+                       // TODO log error?
+                       continue
+               }
+               dsIDs = append(dsIDs, *ds.ID)
+       }
+
+       isMid := strings.HasPrefix(server.Type, string(tc.CacheTypeMid))
+
+       serverIDs := ([]int)(nil)
+       if !isMid {
+               // mids use all servers, so pass nil=all. Edges only use this 
current server
+               serverIDs = append(serverIDs, server.ID)
+       }
+
+       dsServers, err := GetDeliveryServiceServers(cfg, dsIDs, serverIDs)
+       if err != nil {
+               return "", errors.New("getting parent.config cachegroup parent 
server delivery service servers: " + err.Error())
+       }
+
+       dssMap := map[int]map[int]struct{}{} // set of map[dsID][serverID]
+       for _, dss := range dsServers {
+               if dss.Server == nil || dss.DeliveryService == nil {
+                       continue // TODO log?
+               }
+               if dssMap[*dss.DeliveryService] == nil {
+                       dssMap[*dss.DeliveryService] = map[int]struct{}{}
+               }
+               dssMap[*dss.DeliveryService][*dss.Server] = struct{}{}
+       }
+
+       useInactive := false
+       if !isMid {
+               // mids get inactive DSes, edges don't. This is how it's always 
behaved, not necessarily how it should.
+               useInactive = true
+       }
+
+       filteredDSes := []tc.DeliveryServiceNullable{}
+       for _, ds := range deliveryServices {
+               if ds.ID == nil {
+                       continue // TODO log?
+               }
+               if ds.Active == nil {
+                       continue // TODO log?
+               }
+               if _, ok := dssMap[*ds.ID]; !ok {
+                       continue
+               }
+               if !useInactive && !*ds.Active {
+                       continue
+               }
+               filteredDSes = append(filteredDSes, ds)
+       }
+
+       dsRegexes, err := GetDeliveryServiceRegexes(cfg)
+       if err != nil {
+               return "", errors.New("getting delivery service regexes: " + 
err.Error())
+       }
+
+       dsRegexMap := map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex{}
+       for _, dsRegex := range dsRegexes {
+               
sort.Sort(DeliveryServiceRegexesSortByTypeThenSetNum(dsRegex.Regexes))
+               dsRegexMap[tc.DeliveryServiceName(dsRegex.DSName)] = 
dsRegex.Regexes
+       }
+
+       remapConfigDSData := []atscfg.RemapConfigDSData{}
+       for _, ds := range filteredDSes {
+               if ds.ID == nil || ds.Type == nil || ds.XMLID == nil || ds.DSCP 
== nil || ds.Active == nil {
+                       continue // TODO log error?
+               }
+               // TODO sort by DS ID? the old Perl query does, but it 
shouldn't be necessary, except for determinism.
+               // TODO warn if no regexes?
+               for _, dsRegex := range 
dsRegexMap[tc.DeliveryServiceName(*ds.XMLID)] {
+                       remapConfigDSData = append(remapConfigDSData, 
atscfg.RemapConfigDSData{
+                               ID:                       *ds.ID,
+                               Type:                     *ds.Type,
+                               OriginFQDN:               ds.OrgServerFQDN,
+                               MidHeaderRewrite:         ds.MidHeaderRewrite,
+                               CacheURL:                 ds.CacheURL,
+                               RangeRequestHandling:     
ds.RangeRequestHandling,
+                               RemapText:                ds.RemapText,
+                               EdgeHeaderRewrite:        ds.EdgeHeaderRewrite,
+                               SigningAlgorithm:         ds.SigningAlgorithm,
+                               Name:                     *ds.XMLID,
+                               QStringIgnore:            ds.QStringIgnore,
+                               RegexRemap:               ds.RegexRemap,
+                               FQPacingRate:             ds.FQPacingRate,
+                               DSCP:                     *ds.DSCP,
+                               RoutingName:              ds.RoutingName,
+                               Pattern:                  
util.StrPtr(dsRegex.Pattern),
+                               RegexType:                
util.StrPtr(dsRegex.Type),
+                               Domain:                   
util.StrPtr(serverCDNDomain), // note this is intentionally the CDN domain, not 
the DS or Server Domain. Must be the remap domain.
+                               OriginShield:             ds.OriginShield,
+                               ProfileID:                ds.ProfileID,
+                               Protocol:                 ds.Protocol,
+                               AnonymousBlockingEnabled: 
ds.AnonymousBlockingEnabled,
+                               Active:                   *ds.Active,
+                       })
+               }
+       }
+
+       serverProfileParams, err := GetProfileParameters(cfg, server.Profile)
+       if err != nil {
+               return "", errors.New("getting profile parameters from server 
(profile '" + server.Profile + ": " + err.Error())
+       }
+
+       serverPackageParamData := map[string]string{}
+       for _, param := range serverProfileParams {
+               if param.ConfigFile != "package" { // TODO put in const
+                       continue
+               }
+
+               if param.Name == "location" { // TODO put in const
+                       continue
+               }
+
+               paramName := param.Name
+               // some files have multiple lines with the same key... handle 
that with param id.
+               if _, ok := serverPackageParamData[param.Name]; ok {
+                       paramName += "__" + strconv.Itoa(param.ID)
+               }
+               paramValue := param.Value
+               if paramValue == "STRING __HOSTNAME__" {
+                       paramValue = server.HostName + "." + server.DomainName 
// TODO strings.Replace to replace all anywhere, instead of just an exact match?
+               }
+               serverPackageParamData[paramName] = paramValue
+       }
+
+       cacheURLParams := map[string]string{}
+       for _, param := range serverProfileParams {
+               if param.ConfigFile != atscfg.CacheURLParameterConfigFile {
+                       continue
+               }
+               if existingVal, ok := cacheURLParams[param.Name]; ok {
+                       log.Warnln("generating remap.config: server profile '" 
+ server.Profile + "' cacheurl.config has multiple parameters for '" + 
param.Name + "' - using '" + existingVal + "' and ignoring the rest!")
+                       continue
+               }
+               cacheURLParams[param.Name] = param.Value
+       }
+
+       cacheKeyParams, err := GetConfigFileParameters(cfg, 
atscfg.CacheKeyParameterConfigFile)
+       if err != nil {
+               return "", errors.New("getting cache key parameters: " + 
err.Error())
+       }
+
+       cacheKeyParamsWithProfiles, err := 
TCParamsToParamsWithProfiles(cacheKeyParams)
+       if err != nil {
+               return "", errors.New("decoding cache key parameter profiles: " 
+ err.Error())
+       }
+
+       cacheKeyParamsWithProfilesMap := 
ParameterWithProfilesToMap(cacheKeyParamsWithProfiles)
+
+       dsProfileNamesToIDs := map[string]int{}
+       for _, ds := range filteredDSes {
+               if ds.ProfileID == nil || ds.ProfileName == nil {
+                       continue // TODO log
+               }
+               dsProfileNamesToIDs[*ds.ProfileName] = *ds.ProfileID
+       }
+
+       dsProfilesCacheKeyConfigParams := map[int]map[string]string{}
+       for _, param := range cacheKeyParamsWithProfilesMap {
+               for dsProfileName, dsProfileID := range dsProfileNamesToIDs {
+                       if _, ok := param.ProfileNames[dsProfileName]; ok {
+                               if _, ok := 
dsProfilesCacheKeyConfigParams[dsProfileID]; !ok {
+                                       
dsProfilesCacheKeyConfigParams[dsProfileID] = map[string]string{}
+                               }
+                               if _, ok := 
dsProfilesCacheKeyConfigParams[dsProfileID][param.Name]; ok {
+                                       // TODO warn
+                                       continue
+                               }
+                               
dsProfilesCacheKeyConfigParams[dsProfileID][param.Name] = param.Value
+                       }
+               }
+       }
+
+       // TODO get dses first, so we can get the profile names-to-IDs without 
fetching all profiles
+
+       // TODO put parentcg logic in func, to remove duplication with 
parent.config
+
+       cacheGroups, err := GetCacheGroups(cfg)
+       if err != nil {
+               return "", errors.New("getting cachegroups: " + err.Error())
+       }
+
+       cgMap := map[string]tc.CacheGroupNullable{}
+       for _, cg := range cacheGroups {
+               if cg.Name == nil {
+                       return "", errors.New("got cachegroup with nil name!'")
+               }
+               cgMap[*cg.Name] = cg
+       }
+
+       serverCG, ok := cgMap[server.Cachegroup]
+       if !ok {
+               return "", errors.New("server '" + serverNameOrID + "' 
cachegroup '" + server.Cachegroup + "' not found in CacheGroups")
+       }
+
+       parentCGID := -1
+       parentCGType := ""
+       if serverCG.ParentName != nil && *serverCG.ParentName != "" {
+               parentCG, ok := cgMap[*serverCG.ParentName]
+               if !ok {
+                       return "", errors.New("server '" + serverNameOrID + "' 
cachegroup '" + server.Cachegroup + "' parent '" + *serverCG.ParentName + "' 
not found in CacheGroups")
+               }
+               if parentCG.ID == nil {
+                       return "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
+               }
+               parentCGID = *parentCG.ID
+
+               if parentCG.Type == nil {
+                       return "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
+               }
+               parentCGType = *parentCG.Type
+       }
+
+       secondaryParentCGID := -1
+       secondaryParentCGType := ""
+       if serverCG.SecondaryParentName != nil && *serverCG.SecondaryParentName 
!= "" {
+               parentCG, ok := cgMap[*serverCG.SecondaryParentName]
+               if !ok {
+                       return "", errors.New("server '" + serverNameOrID + "' 
cachegroup '" + server.Cachegroup + "' secondary parent '" + 
*serverCG.SecondaryParentName + "' not found in CacheGroups")
+               }
+
+               if parentCG.ID == nil {
+                       return "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
+               }
+               secondaryParentCGID = *parentCG.ID
+               if parentCG.Type == nil {
+                       return "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
+               }
+
+               secondaryParentCGType = *parentCG.Type
+       }
+
+       serverInfo := &atscfg.ServerInfo{
+               CacheGroupID:                  server.CachegroupID,
+               CDN:                           tc.CDNName(server.CDNName),
+               CDNID:                         server.CDNID,
+               DomainName:                    serverCDNDomain, // note this is 
intentionally the CDN domain, not the server domain. It's what's remapped to.
+               HostName:                      server.HostName,
+               ID:                            server.ID,
+               IP:                            server.IPAddress,
+               ParentCacheGroupID:            parentCGID,
+               ParentCacheGroupType:          parentCGType,
+               ProfileID:                     
atscfg.ProfileID(server.ProfileID),
+               ProfileName:                   server.Profile,
+               Port:                          server.TCPPort,
+               HTTPSPort:                     server.HTTPSPort,
+               SecondaryParentCacheGroupID:   secondaryParentCGID,
+               SecondaryParentCacheGroupType: secondaryParentCGType,
+               Type:                          server.Type,
+       }
+
+       txt := atscfg.MakeRemapDotConfig(tc.CacheName(serverName), toToolName, 
toURL, atsMajorVer, cacheURLParams, dsProfilesCacheKeyConfigParams, 
serverPackageParamData, serverInfo, remapConfigDSData)
+       return txt, nil
+}
+
+type DeliveryServiceRegexesSortByTypeThenSetNum []tc.DeliveryServiceRegex
+
+func (r DeliveryServiceRegexesSortByTypeThenSetNum) Len() int { return len(r) }
+func (r DeliveryServiceRegexesSortByTypeThenSetNum) Less(i, j int) bool {
+       if rc := strings.Compare(r[i].Type, r[j].Type); rc != 0 {
+               return rc < 0
+       }
+       return r[i].SetNumber < r[j].SetNumber
+}
+func (r DeliveryServiceRegexesSortByTypeThenSetNum) Swap(i, j int) { r[i], 
r[j] = r[j], r[i] }
diff --git a/traffic_ops/ort/atstccfg/routing.go 
b/traffic_ops/ort/atstccfg/routing.go
index 5d67a42..27e3086 100644
--- a/traffic_ops/ort/atstccfg/routing.go
+++ b/traffic_ops/ort/atstccfg/routing.go
@@ -125,6 +125,7 @@ func ProfileConfigFileFuncs() map[string]func(cfg TCCfg, 
serverNameOrID string)
 func ServerConfigFileFuncs() map[string]func(cfg TCCfg, serverNameOrID string) 
(string, error) {
        return map[string]func(cfg TCCfg, serverNameOrID string) (string, 
error){
                "parent.config": GetConfigFileServerParentDotConfig,
+               "remap.config":  GetConfigFileServerRemapDotConfig,
        }
 }
 
diff --git a/traffic_ops/ort/atstccfg/toreq.go 
b/traffic_ops/ort/atstccfg/toreq.go
index 56f50e0..76e4935 100644
--- a/traffic_ops/ort/atstccfg/toreq.go
+++ b/traffic_ops/ort/atstccfg/toreq.go
@@ -23,10 +23,10 @@ import (
        "errors"
        "sort"
        "strconv"
-       "strings"
 
        "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
 )
 
 func GetProfile(cfg TCCfg, profileID int) (tc.Profile, error) {
@@ -171,22 +171,25 @@ func GetCacheGroups(cfg TCCfg) ([]tc.CacheGroupNullable, 
error) {
        return cacheGroups, nil
 }
 
-func GetDeliveryServiceServers(cfg TCCfg, serverIDs []int) 
([]tc.DeliveryServiceServer, error) {
+func GetDeliveryServiceServers(cfg TCCfg, dsIDs []int, serverIDs []int) 
([]tc.DeliveryServiceServer, error) {
        serverIDsSorted := make([]int, 0, len(serverIDs))
        for _, id := range serverIDs {
                serverIDsSorted = append(serverIDsSorted, id)
        }
        sort.Ints(serverIDsSorted)
 
-       serverIDStrs := []string{}
-       for _, id := range serverIDs {
-               serverIDStrs = append(serverIDStrs, strconv.Itoa(id))
+       dsIDsSorted := make([]int, 0, len(dsIDs))
+       for _, id := range dsIDs {
+               dsIDsSorted = append(dsIDsSorted, id)
        }
-       serverIDsStr := strings.Join(serverIDStrs, "-")
+       sort.Ints(dsIDsSorted)
+
+       serverIDsStr := util.HashInts(serverIDsSorted)
+       dsIDsStr := util.HashInts(dsIDsSorted)
 
        dsServers := []tc.DeliveryServiceServer{}
        // TODO make this filename shorter (but still unique) somehow. The 
filename is almost always too long.
-       err := GetCachedJSON(cfg, 
"deliveryservice_servers_"+serverIDsStr+".json", &dsServers, func(obj 
interface{}) error {
+       err := GetCachedJSON(cfg, 
"deliveryservice_servers_d_"+dsIDsStr+"_s_"+serverIDsStr+".json", &dsServers, 
func(obj interface{}) error {
                const noLimit = 999999 // TODO add "no limit" param to DSS 
endpoint
                toDSS, reqInf, err := 
(*cfg.TOClient).GetDeliveryServiceServersWithLimits(noLimit, nil, serverIDs)
                if err != nil {
@@ -343,3 +346,20 @@ func GetParametersByName(cfg TCCfg, paramName string) 
([]tc.Parameter, error) {
        }
        return params, nil
 }
+
+func GetDeliveryServiceRegexes(cfg TCCfg) ([]tc.DeliveryServiceRegexes, error) 
{
+       regexes := []tc.DeliveryServiceRegexes{}
+       err := GetCachedJSON(cfg, "ds_regexes.json", &regexes, func(obj 
interface{}) error {
+               toRegexes, reqInf, err := 
(*cfg.TOClient).GetDeliveryServiceRegexes()
+               if err != nil {
+                       return errors.New("getting ds regexes from Traffic Ops 
'" + MaybeIPStr(reqInf) + "': " + err.Error())
+               }
+               regexes := obj.(*[]tc.DeliveryServiceRegexes)
+               *regexes = toRegexes
+               return nil
+       })
+       if err != nil {
+               return nil, errors.New("getting ds regexes: " + err.Error())
+       }
+       return regexes, nil
+}
diff --git a/traffic_ops/ort/atstccfg/trafficops.go 
b/traffic_ops/ort/atstccfg/trafficops.go
index a503b37..15719a8 100644
--- a/traffic_ops/ort/atstccfg/trafficops.go
+++ b/traffic_ops/ort/atstccfg/trafficops.go
@@ -282,3 +282,20 @@ type ParameterWithProfiles struct {
        tc.Parameter
        ProfileNames []string
 }
+
+type ParameterWithProfilesMap struct {
+       tc.Parameter
+       ProfileNames map[string]struct{}
+}
+
+func ParameterWithProfilesToMap(tcParams []ParameterWithProfiles) 
[]ParameterWithProfilesMap {
+       params := []ParameterWithProfilesMap{}
+       for _, tcParam := range tcParams {
+               param := ParameterWithProfilesMap{Parameter: tcParam.Parameter, 
ProfileNames: map[string]struct{}{}}
+               for _, profile := range tcParam.ProfileNames {
+                       param.ProfileNames[profile] = struct{}{}
+               }
+               params = append(params, param)
+       }
+       return params
+}
diff --git a/traffic_ops/testing/api/v14/parentdotconfig_test.go 
b/traffic_ops/testing/api/v14/parentdotconfig_test.go
index 909e01b..1321f9a 100644
--- a/traffic_ops/testing/api/v14/parentdotconfig_test.go
+++ b/traffic_ops/testing/api/v14/parentdotconfig_test.go
@@ -20,8 +20,6 @@ import (
        "strconv"
        "strings"
        "testing"
-
-       "github.com/apache/trafficcontrol/lib/go-log"
 )
 
 func TestParentDotConfig(t *testing.T) {
@@ -71,8 +69,6 @@ func GetTestParentDotConfig(t *testing.T) {
 }
 
 func CreateTestDeliveryServiceServers(t *testing.T) {
-       log.Debugln("DeleteTestDeliveryServiceServers")
-
        dses, _, err := TOSession.GetDeliveryServices()
        if err != nil {
                t.Errorf("cannot GET DeliveryServices: %v\n", err)
@@ -80,7 +76,6 @@ func CreateTestDeliveryServiceServers(t *testing.T) {
        if len(dses) < 1 {
                t.Errorf("GET DeliveryServices returned no dses, must have at 
least 1 to test ds-servers")
        }
-       ds := dses[0]
 
        servers, _, err := TOSession.GetServers()
        if err != nil {
@@ -89,11 +84,17 @@ func CreateTestDeliveryServiceServers(t *testing.T) {
        if len(servers) < 1 {
                t.Errorf("GET Servers returned no dses, must have at least 1 to 
test ds-servers")
        }
-       server := servers[0]
 
-       _, err = TOSession.CreateDeliveryServiceServers(ds.ID, 
[]int{server.ID}, true)
-       if err != nil {
-               t.Errorf("POST delivery service servers: %v\n", err)
+       for _, ds := range dses {
+               serverIDs := []int{}
+               for _, server := range servers {
+                       serverIDs = append(serverIDs, server.ID)
+               }
+
+               _, err = TOSession.CreateDeliveryServiceServers(ds.ID, 
serverIDs, true)
+               if err != nil {
+                       t.Errorf("POST delivery service servers: %v\n", err)
+               }
        }
 }
 
@@ -117,7 +118,7 @@ func DeleteTestDeliveryServiceServersCreated(t *testing.T) {
        }
        server := servers[0]
 
-       dsServers, _, err := TOSession.GetDeliveryServiceServers()
+       dsServers, _, err := TOSession.GetDeliveryServiceServersN(1000000)
        if err != nil {
                t.Errorf("GET delivery service servers: %v\n", err)
        }
diff --git a/traffic_ops/testing/api/v14/remapdotconfig_test.go 
b/traffic_ops/testing/api/v14/remapdotconfig_test.go
new file mode 100644
index 0000000..80a3bc6
--- /dev/null
+++ b/traffic_ops/testing/api/v14/remapdotconfig_test.go
@@ -0,0 +1,95 @@
+package v14
+
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "net/url"
+       "strconv"
+       "strings"
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+func TestRemapDotConfig(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, 
DeliveryServices}, func() {
+               defer DeleteTestDeliveryServiceServersCreated(t)
+               CreateTestDeliveryServiceServers(t)
+               GetTestRemapDotConfig(t)
+       })
+}
+
+func GetTestRemapDotConfig(t *testing.T) {
+       dsServers, _, err := TOSession.GetDeliveryServiceServers()
+       if err != nil {
+               t.Fatalf("GET delivery service servers: %v\n", err)
+       } else if len(dsServers.Response) == 0 {
+               t.Fatalf("GET delivery service servers: no servers found\n")
+       } else if dsServers.Response[0].Server == nil {
+               t.Fatalf("GET delivery service servers: returned nil server\n")
+       } else if dsServers.Response[0].DeliveryService == nil {
+               t.Fatalf("GET delivery service servers: returned nil ds\n")
+       }
+       serverID := *dsServers.Response[0].Server
+
+       ds := (*tc.DeliveryService)(nil)
+       for _, dsServer := range dsServers.Response {
+               ds, _, err = 
TOSession.GetDeliveryService(strconv.Itoa(*dsServer.DeliveryService))
+               if err != nil {
+                       t.Fatalf("Getting ds %+v: "+err.Error()+"\n", 
*dsServers.Response[0].DeliveryService)
+               } else if ds == nil {
+                       t.Fatalf("Getting ds %+v: "+"got nil response"+"\n", 
*dsServers.Response[0].DeliveryService)
+               } else if ds.OrgServerFQDN == "" {
+                       t.Fatalf("Getting ds %+v: "+"got empty 
ds.OrgServerFQDN"+"\n", *dsServers.Response[0].DeliveryService)
+               }
+               if ds.Type == tc.DSTypeAnyMap {
+                       continue
+               }
+               break
+       }
+       if ds == nil || ds.XMLID == "" {
+               t.Fatalf("no Delivery Service found with assigned servers that 
isn't an ANY_MAP service, can't test remap.config")
+       }
+
+       originURI, err := url.Parse(ds.OrgServerFQDN)
+       if err != nil {
+               t.Fatalf("Getting ds %+v: "+" ds.OrgServerFQDN '%+v' failed to 
parse as a URL: %+v\n", *dsServers.Response[0].DeliveryService, 
ds.OrgServerFQDN, err)
+       }
+       originHost := originURI.Hostname()
+
+       remapDotConfig, _, err := TOSession.GetATSServerConfig(serverID, 
"remap.config")
+       if err != nil {
+               t.Fatalf("Getting server %+v config remap.config: 
"+err.Error()+"\n", serverID)
+       }
+
+       if !strings.Contains(remapDotConfig, originHost) {
+               t.Errorf("expected: remap.config to contain delivery service 
origin FQDN '%+v' host '%+v', actual: '''%+v'''", ds.OrgServerFQDN, originHost, 
remapDotConfig)
+       }
+
+       remapDotConfigLines := strings.Split(remapDotConfig, "\n")
+       for i, line := range remapDotConfigLines {
+               line = strings.TrimSpace(line)
+               if len(line) == 0 {
+                       continue
+               }
+               if line[0] == '#' {
+                       continue
+               }
+               if !strings.HasPrefix(line, "map") {
+                       t.Errorf("expected: remap.config line %v to start with 
'map', actual: '%v'\n", i, line)
+               }
+       }
+}
diff --git a/traffic_ops/testing/api/v14/tc-fixtures.json 
b/traffic_ops/testing/api/v14/tc-fixtures.json
index e0eb4e0..1e1e647 100644
--- a/traffic_ops/testing/api/v14/tc-fixtures.json
+++ b/traffic_ops/testing/api/v14/tc-fixtures.json
@@ -481,7 +481,7 @@
             "rangeRequestHandling": 0,
             "regexRemap": "",
             "regionalGeoBlocking": false,
-            "remapText": "some raw remap text",
+            "remapText": "map some raw remap text",
             "routingName": "",
             "signed": false,
             "signingAlgorithm": "url_sig",
diff --git a/traffic_ops/traffic_ops_golang/ats/meta.go 
b/traffic_ops/traffic_ops_golang/ats/atsserver/meta.go
similarity index 64%
rename from traffic_ops/traffic_ops_golang/ats/meta.go
rename to traffic_ops/traffic_ops_golang/ats/atsserver/meta.go
index c2dbe9c..d675b7e 100644
--- a/traffic_ops/traffic_ops_golang/ats/meta.go
+++ b/traffic_ops/traffic_ops_golang/ats/atsserver/meta.go
@@ -1,4 +1,4 @@
-package ats
+package atsserver
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -28,6 +28,7 @@ import (
        "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats"
 )
 
 func GetConfigMetaData(w http.ResponseWriter, r *http.Request) {
@@ -38,13 +39,13 @@ func GetConfigMetaData(w http.ResponseWriter, r 
*http.Request) {
        }
        defer inf.Close()
 
-       serverName, userErr, sysErr, errCode := 
getServerNameFromNameOrID(inf.Tx.Tx, inf.Params["server-name-or-id"])
+       serverName, userErr, sysErr, errCode := 
ats.GetServerNameFromNameOrID(inf.Tx.Tx, inf.Params["server-name-or-id"])
        if userErr != nil || sysErr != nil {
                api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
                return
        }
 
-       server, ok, err := getServerInfoByHost(inf.Tx.Tx, serverName)
+       server, ok, err := ats.GetServerInfoByHost(inf.Tx.Tx, serverName)
        if err != nil {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("GetConfigMetaData getting server info: "+err.Error()))
                return
@@ -53,7 +54,7 @@ func GetConfigMetaData(w http.ResponseWriter, r 
*http.Request) {
                return
        }
 
-       tmParams, err := GetTMParams(inf.Tx.Tx)
+       tmParams, err := ats.GetTMParams(inf.Tx.Tx)
        if err != nil {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("GetConfigMetaData getting tm.url parameter: "+err.Error()))
                return
@@ -78,7 +79,7 @@ func GetConfigMetaData(w http.ResponseWriter, r 
*http.Request) {
                ConfigFiles: []tc.ATSConfigMetaDataConfigFile{},
        }
 
-       locationParams, err := GetLocationParams(inf.Tx.Tx, 
int(server.ProfileID))
+       locationParams, err := ats.GetLocationParams(inf.Tx.Tx, 
int(server.ProfileID))
        if err != nil {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("GetConfigMetaData getting location parameters: "+err.Error()))
                return
@@ -86,7 +87,7 @@ func GetConfigMetaData(w http.ResponseWriter, r 
*http.Request) {
 
        if locationParams["remap.config"].Location != "" {
                configLocation := locationParams["remap.config"].Location
-               uriSignedDSes, err := GetServerURISignedDSes(inf.Tx.Tx, 
server.HostName, server.Port)
+               uriSignedDSes, err := ats.GetServerURISignedDSes(inf.Tx.Tx, 
server.HostName, server.Port)
                if err != nil {
                        api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("GetConfigMetaData getting 
server uri-signed dses: "+err.Error()))
                        return
@@ -137,104 +138,6 @@ func GetConfigMetaData(w http.ResponseWriter, r 
*http.Request) {
        api.WriteRespRaw(w, r, atsData)
 }
 
-// GetTMParams returns the global "tm.url" and "tm.rev_proxy.url" parameters, 
and any error. If either param doesn't exist, an empty string is returned 
without error.
-func GetTMParams(tx *sql.Tx) (TMParams, error) {
-       rows, err := tx.Query(`SELECT name, value from parameter where 
config_file = 'global' AND (name = 'tm.url' OR name = 'tm.rev_proxy.url')`)
-       if err != nil {
-               return TMParams{}, errors.New("querying: " + err.Error())
-       }
-       defer rows.Close()
-
-       p := TMParams{}
-       for rows.Next() {
-               name := ""
-               val := ""
-               if err := rows.Scan(&name, &val); err != nil {
-                       return TMParams{}, errors.New("scanning: " + 
err.Error())
-               }
-               if name == "tm.url" {
-                       p.URL = val
-               } else if name == "tm.rev_proxy.url" {
-                       p.ReverseProxyURL = val
-               } else {
-                       return TMParams{}, errors.New("querying got unexpected 
parameter: " + name + " (value: '" + val + "')") // should never happen
-               }
-       }
-       return p, nil
-}
-
-// GetLocationParams returns a map[configFile]locationParams, and any error. 
If either param doesn't exist, an empty string is returned without error.
-func GetLocationParams(tx *sql.Tx, profileID int) 
(map[string]ConfigProfileParams, error) {
-       qry := `
-SELECT
-  p.name,
-  p.config_file,
-  p.value
-FROM
-  parameter p
-  JOIN profile_parameter pp ON pp.parameter = p.id
-WHERE
-  pp.profile = $1
-`
-       rows, err := tx.Query(qry, profileID)
-       if err != nil {
-               return nil, errors.New("querying: " + err.Error())
-       }
-       defer rows.Close()
-
-       params := map[string]ConfigProfileParams{}
-       for rows.Next() {
-               name := ""
-               file := ""
-               val := ""
-               if err := rows.Scan(&name, &file, &val); err != nil {
-                       return nil, errors.New("scanning: " + err.Error())
-               }
-               if name == "location" {
-                       p := params[file]
-                       p.FileNameOnDisk = file
-                       p.Location = val
-                       params[file] = p
-               } else if name == "URL" {
-                       p := params[file]
-                       p.URL = val
-                       params[file] = p
-               }
-       }
-       return params, nil
-}
-
-// GetServerURISignedDSes returns a list of delivery service names which have 
the given server assigned and have URI signing enabled, and any error.
-func GetServerURISignedDSes(tx *sql.Tx, serverHostName string, serverPort int) 
([]tc.DeliveryServiceName, error) {
-       qry := `
-SELECT
-  ds.xml_id
-FROM
-  deliveryservice ds
-  JOIN deliveryservice_server dss ON ds.id = dss.deliveryservice
-  JOIN server s ON s.id = dss.server
-WHERE
-  s.host_name = $1
-  AND s.tcp_port = $2
-  AND ds.signing_algorithm = 'uri_signing'
-`
-       rows, err := tx.Query(qry, serverHostName, serverPort)
-       if err != nil {
-               return nil, errors.New("querying: " + err.Error())
-       }
-       defer rows.Close()
-
-       dses := []tc.DeliveryServiceName{}
-       for rows.Next() {
-               ds := tc.DeliveryServiceName("")
-               if err := rows.Scan(&ds); err != nil {
-                       return nil, errors.New("scanning: " + err.Error())
-               }
-               dses = append(dses, ds)
-       }
-       return dses, nil
-}
-
 func getServerScope(tx *sql.Tx, cfgFile string, serverType string) 
(tc.ATSConfigMetaDataConfigFileScope, error) {
        switch {
        case cfgFile == "cache.config" && tc.CacheTypeFromString(serverType) == 
tc.CacheTypeMid:
@@ -306,7 +209,7 @@ func getScope(tx *sql.Tx, cfgFile string) 
(tc.ATSConfigMetaDataConfigFileScope,
                return tc.ATSConfigMetaDataConfigFileScopeCDNs, nil
        }
 
-       scope, ok, err := GetFirstScopeParameter(tx, cfgFile)
+       scope, ok, err := ats.GetFirstScopeParameter(tx, cfgFile)
        if err != nil {
                return tc.ATSConfigMetaDataConfigFileScopeInvalid, 
errors.New("getting scope parameter: " + err.Error())
        }
@@ -315,27 +218,3 @@ func getScope(tx *sql.Tx, cfgFile string) 
(tc.ATSConfigMetaDataConfigFileScope,
        }
        return tc.ATSConfigMetaDataConfigFileScope(scope), nil
 }
-
-type TMParams struct {
-       URL             string
-       ReverseProxyURL string
-}
-
-type ConfigProfileParams struct {
-       FileNameOnDisk string
-       Location       string
-       URL            string
-       APIURI         string
-}
-
-// GetFirstScopeParameter returns the value of the arbitrarily-first parameter 
with the name 'scope' and the given config file, whether a parameter was found, 
and any error.
-func GetFirstScopeParameter(tx *sql.Tx, cfgFile string) (string, bool, error) {
-       v := ""
-       if err := tx.QueryRow(`SELECT p.value FROM parameter p WHERE 
p.config_file = $1 AND p.name = 'scope'`, cfgFile).Scan(&v); err != nil {
-               if err == sql.ErrNoRows {
-                       return "", false, nil
-               }
-               return "", false, errors.New("querying first scope parameter: " 
+ err.Error())
-       }
-       return v, true, nil
-}
diff --git a/traffic_ops/traffic_ops_golang/ats/parentdotconfig.go 
b/traffic_ops/traffic_ops_golang/ats/atsserver/parentdotconfig.go
similarity index 99%
rename from traffic_ops/traffic_ops_golang/ats/parentdotconfig.go
rename to traffic_ops/traffic_ops_golang/ats/atsserver/parentdotconfig.go
index 5396e62..59c6647 100644
--- a/traffic_ops/traffic_ops_golang/ats/parentdotconfig.go
+++ b/traffic_ops/traffic_ops_golang/ats/atsserver/parentdotconfig.go
@@ -1,4 +1,4 @@
-package ats
+package atsserver
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -30,6 +30,7 @@ import (
        "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats"
 
        "github.com/lib/pq"
 )
@@ -72,7 +73,7 @@ func GetParentDotConfig(w http.ResponseWriter, r 
*http.Request) {
                return
        }
 
-       toolName, toURL, err := GetToolNameAndURL(inf.Tx.Tx)
+       toolName, toURL, err := ats.GetToolNameAndURL(inf.Tx.Tx)
        if err != nil {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting toolname and TO url parameters: "+err.Error()))
                return
@@ -166,7 +167,7 @@ FROM
 // GetATSMajorVersion returns the major version of the given profile's package 
trafficserver parameter.
 // If no parameter exists, this does not return an error, but rather logs a 
warning and uses DefaultATSVersion.
 func GetATSMajorVersion(tx *sql.Tx, serverProfileID int) (int, error) {
-       atsVersion, _, err := GetProfileParamValue(tx, serverProfileID, 
"package", "trafficserver")
+       atsVersion, _, err := ats.GetProfileParamValue(tx, serverProfileID, 
"package", "trafficserver")
        if err != nil {
                return 0, errors.New("getting profile param value: " + 
err.Error())
        }
diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/remapdotconfig.go 
b/traffic_ops/traffic_ops_golang/ats/atsserver/remapdotconfig.go
new file mode 100644
index 0000000..502af24
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/ats/atsserver/remapdotconfig.go
@@ -0,0 +1,93 @@
+package atsserver
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "errors"
+       "io"
+       "net/http"
+
+       "github.com/apache/trafficcontrol/lib/go-atscfg"
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats"
+)
+
+func GetServerConfigRemap(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, 
[]string{"server-name-or-id"}, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       serverName, userErr, sysErr, errCode := 
ats.GetServerNameFromNameOrID(inf.Tx.Tx, inf.Params["server-name-or-id"])
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+
+       toToolName, toURL, err := ats.GetToolNameAndURL(inf.Tx.Tx)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting tool name and url: "+err.Error()))
+               return
+       }
+
+       atsMajorVersion, err := ats.GetATSMajorVersionFromServerName(inf.Tx.Tx, 
serverName)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting ATS major version: "+err.Error()))
+               return
+       }
+
+       cacheURLConfigParams, err := ats.GetServerProfileParamData(inf.Tx.Tx, 
serverName, "cacheurl.config")
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting cacheurl.config params: "+err.Error()))
+               return
+       }
+
+       serverInfo, ok, err := ats.GetServerInfoByHost(inf.Tx.Tx, serverName)
+       if !ok {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, 
errors.New("server not found"), nil)
+               return
+       }
+
+       remapDSData, err := ats.GetRemapDSData(inf.Tx.Tx, serverInfo)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("Getting remap ds data: "+err.Error()))
+               return
+       }
+
+       dsProfilesCacheKeyConfigParams, err := 
ats.GetProfilesParamData(inf.Tx.Tx, atscfg.DSProfileIDs(remapDSData), 
"cachekey.config")
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("Getting profiles param data for cachekey: "+err.Error()))
+               return
+       }
+
+       serverPackageParamData, err := ats.GetServerParamData(inf.Tx.Tx, 
int(serverInfo.ProfileID), "package", serverInfo.HostName, 
serverInfo.DomainName)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("Getting server package param data: "+err.Error()))
+               return
+       }
+
+       txt := atscfg.MakeRemapDotConfig(serverName, toToolName, toURL, 
atsMajorVersion, cacheURLConfigParams, dsProfilesCacheKeyConfigParams, 
serverPackageParamData, serverInfo, remapDSData)
+
+       w.Header().Set(tc.ContentType, tc.ContentTypeTextPlain)
+       io.WriteString(w, txt)
+}
diff --git a/traffic_ops/traffic_ops_golang/ats/config.go 
b/traffic_ops/traffic_ops_golang/ats/config.go
index c0b999d..ca7c82f 100644
--- a/traffic_ops/traffic_ops_golang/ats/config.go
+++ b/traffic_ops/traffic_ops_golang/ats/config.go
@@ -106,29 +106,6 @@ func getCDNNameFromNameOrID(tx *sql.Tx, cdnNameOrID 
string) (string, error, erro
        return cdnName, nil, nil, http.StatusOK
 }
 
-// getServerNameFromNameOrID returns the server name from a parameter which 
may be the name or ID.
-// This also checks and verifies the existence of the given server, and 
returns an appropriate user error if it doesn't exist.
-// Returns the name, any user error, any system error, and any error code.
-func getServerNameFromNameOrID(tx *sql.Tx, serverNameOrID string) (string, 
error, error, int) {
-       if serverID, err := strconv.Atoi(serverNameOrID); err == nil {
-               serverName, ok, err := dbhelpers.GetServerNameFromID(tx, 
int64(serverID))
-               if err != nil {
-                       return "", nil, fmt.Errorf("getting server name from id 
%v: %v", serverID, err), http.StatusInternalServerError
-               } else if !ok {
-                       return "", errors.New("server not found"), nil, 
http.StatusNotFound
-               }
-               return string(serverName), nil, nil, http.StatusOK
-       }
-
-       serverName := serverNameOrID
-       if ok, err := dbhelpers.ServerExists(serverName, tx); err != nil {
-               return "", nil, fmt.Errorf("checking server name '%v' 
existence: %v", serverName, err), http.StatusInternalServerError
-       } else if !ok {
-               return "", errors.New("server not found"), nil, 
http.StatusNotFound
-       }
-       return serverName, nil, nil, http.StatusOK
-}
-
 func HeaderComment(tx *sql.Tx, name string) (string, error) {
        nameVersionStr, err := GetNameVersionString(tx)
        if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/ats/db.go 
b/traffic_ops/traffic_ops_golang/ats/db.go
index 90d3b87..69e5d56 100644
--- a/traffic_ops/traffic_ops_golang/ats/db.go
+++ b/traffic_ops/traffic_ops_golang/ats/db.go
@@ -22,12 +22,79 @@ package ats
 import (
        "database/sql"
        "errors"
+       "fmt"
+       "net/http"
+       "strconv"
 
        "github.com/apache/trafficcontrol/lib/go-atscfg"
        "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/lib/go-tc"
+       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+
+       "github.com/lib/pq"
 )
 
+// RemapDotConfigIncludeInactiveDeliveryServices is whether delivery services 
with 'active' false are included in the remap.config.
+const RemapDotConfigIncludeInactiveDeliveryServices = true
+
+// getProfileData returns the necessary info about the profile, whether it 
exists, and any error.
+func getProfileData(tx *sql.Tx, id int) (ProfileData, bool, error) {
+       qry := `
+SELECT
+  p.name
+FROM
+  profile p
+WHERE
+  p.id = $1
+`
+       v := ProfileData{ID: id}
+       if err := tx.QueryRow(qry, id).Scan(&v.Name); err != nil {
+               if err == sql.ErrNoRows {
+                       return ProfileData{}, false, nil
+               }
+               return ProfileData{}, false, errors.New("querying: " + 
err.Error())
+       }
+       return v, true, nil
+}
+
+// GetProfilesParamData returns a map[profileID][paramName]paramVal
+func GetProfilesParamData(tx *sql.Tx, profileIDs []int, configFile string) 
(map[int]map[string]string, error) {
+       qry := `
+SELECT
+  pr.id,
+  p.name,
+  p.value
+FROM
+  profile pr
+  JOIN profile_parameter pp on pr.id = pp.profile
+  JOIN parameter p on p.id = pp.parameter
+WHERE
+  pr.id = ANY($1)
+  AND p.config_file = $2
+`
+
+       rows, err := tx.Query(qry, pq.Array(profileIDs), configFile)
+       if err != nil {
+               return nil, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       profilesParams := map[int]map[string]string{}
+       for rows.Next() {
+               profileID := 0
+               name := ""
+               val := ""
+               if err := rows.Scan(&profileID, &name, &val); err != nil {
+                       return nil, errors.New("scanning: " + err.Error())
+               }
+               if _, ok := profilesParams[profileID]; !ok {
+                       profilesParams[profileID] = map[string]string{}
+               }
+               profilesParams[profileID][name] = val
+       }
+       return profilesParams, nil
+}
+
 func GetProfileParamData(tx *sql.Tx, profileID int, configFile string) 
(map[string]string, error) {
        qry := `
 SELECT
@@ -67,6 +134,64 @@ WHERE
        return params, nil
 }
 
+func GetServerProfileParamData(tx *sql.Tx, serverName tc.CacheName, configFile 
string) (map[string]string, error) {
+       qry := `
+SELECT
+  p.name,
+  p.value
+FROM
+  parameter p
+  JOIN profile_parameter pp on p.id = pp.parameter
+  JOIN server s on s.profile = pp.profile
+WHERE
+  s.host_name = $1
+  AND p.config_file = $2
+`
+       rows, err := tx.Query(qry, serverName, configFile)
+       if err != nil {
+               return nil, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       params := map[string]string{}
+       for rows.Next() {
+               name := ""
+               val := ""
+               if err := rows.Scan(&name, &val); err != nil {
+                       return nil, errors.New("scanning: " + err.Error())
+               }
+               if name == "location" {
+                       continue
+               }
+
+               if _, ok := params[name]; ok {
+                       log.Warnf("Server %v profile has multiple parameters 
'%v' assigned! ATS config generation ignoring value '%v'!", serverName, name, 
params[name])
+               }
+
+               params[name] = val
+       }
+       return params, nil
+}
+
+// GetATSMajorVersion returns the major version of the given profile's package 
trafficserver parameter.
+// If no parameter exists, this does not return an error, but rather logs a 
warning and uses DefaultATSVersion.
+func GetATSMajorVersionFromServerName(tx *sql.Tx, serverName tc.CacheName) 
(int, error) {
+       atsVersion, _, err := GetServerProfileParamValue(tx, serverName, 
"package", "trafficserver")
+       if err != nil {
+               return 0, errors.New("getting profile param value: " + 
err.Error())
+       }
+       if len(atsVersion) == 0 {
+               atsVersion = atscfg.DefaultATSVersion
+               log.Warnln("Parameter package.trafficserver missing for server 
" + string(serverName) + " profile. Assuming version " + atsVersion)
+       }
+
+       atsMajorVer, err := atscfg.GetATSMajorVersionFromATSVersion(atsVersion)
+       if err != nil {
+               return 0, errors.New("ats version parameter '" + atsVersion + 
"' on this profile is not a number (config_file 'package', name 
'trafficserver')")
+       }
+       return atsMajorVer, nil
+}
+
 type ProfileData struct {
        ID   int
        Name string
@@ -152,6 +277,31 @@ WHERE
        return val, true, nil
 }
 
+// GetServerProfileParamValue gets the value of a parameter assigned to a 
server's Profile, by name and config file.
+// Returns the parameter, whether it existed, and any error.
+func GetServerProfileParamValue(tx *sql.Tx, serverName tc.CacheName, 
configFile string, name string) (string, bool, error) {
+       qry := `
+SELECT
+  p.value
+FROM
+  parameter p
+  JOIN profile_parameter pp ON p.id = pp.parameter
+  JOIN server s on s.profile = pp.profile
+WHERE
+  s.host_name = $1
+  AND p.config_file = $2
+  AND p.name = $3
+`
+       val := ""
+       if err := tx.QueryRow(qry, serverName, configFile, name).Scan(&val); 
err != nil {
+               if err == sql.ErrNoRows {
+                       return "", false, nil
+               }
+               return "", false, errors.New("querying: " + err.Error())
+       }
+       return val, true, nil
+}
+
 // GetProfileIDFromName returns the profile's ID, whether it exists, and any 
error.
 func GetProfileIDFromName(tx *sql.Tx, profileName string) (int, bool, error) {
        qry := `SELECT id from profile where name = $1`
@@ -197,3 +347,428 @@ WHERE
        }
        return params, nil
 }
+
+func GetServerParamData(tx *sql.Tx, profileID int, configFile string, 
serverHost string, serverDomain string) (map[string]string, error) {
+       qry := `
+SELECT
+  p.id,
+  p.name,
+  p.value
+FROM
+  parameter p
+  join profile_parameter pp on p.id = pp.parameter
+  JOIN profile pr on p.id = pp.profile
+WHERE
+  pr.id = $1
+  AND p.config_file = $2
+`
+       rows, err := tx.Query(qry, profileID, configFile)
+       if err != nil {
+               return nil, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       params := map[string]string{}
+       for rows.Next() {
+               id := 0
+               name := ""
+               val := ""
+               if err := rows.Scan(&name, &val); err != nil {
+                       return nil, errors.New("scanning: " + err.Error())
+               }
+               if name == "location" {
+                       continue
+               }
+
+               // some files have multiple lines with the same key... handle 
that with param id.
+               key := name
+               if _, ok := params[name]; ok {
+                       key += "__" + strconv.Itoa(id)
+               }
+               if val == "STRING __HOSTNAME__" {
+                       val = serverHost + "." + serverDomain
+               }
+               params[key] = val
+       }
+       return params, nil
+}
+
+type DSData struct {
+       Type       tc.DSType
+       OriginFQDN *string
+}
+
+func GetDSData(tx *sql.Tx, serverID int) ([]DSData, error) {
+       qry := `
+SELECT
+  dstype.name AS ds_type,
+  (SELECT o.protocol::text || \'://\' || o.fqdn || rtrim(concat(\':\', 
o.port::text), \':\')
+    FROM origin o
+    WHERE o.deliveryservice = ds.id
+    AND o.is_primary) as org_server_fqdn
+FROM
+  deliveryservice ds
+  JOIN type as dstype ON ds.type = dstype.id
+WHERE
+  ds.id IN (
+    SELECT DISTINCT deliveryservice
+    FROM deliveryservice_server
+    WHERE server = $1
+  )
+`
+       rows, err := tx.Query(qry, serverID)
+       if err != nil {
+               return nil, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       dses := []DSData{}
+       for rows.Next() {
+               d := DSData{}
+               if err := rows.Scan(&d.Type, &d.OriginFQDN); err != nil {
+                       return nil, errors.New("scanning: " + err.Error())
+               }
+               d.Type = tc.DSTypeFromString(string(d.Type))
+               dses = append(dses, d)
+       }
+       return dses, nil
+}
+
+func GetRemapDSData(tx *sql.Tx, serverInfo *atscfg.ServerInfo) 
([]atscfg.RemapConfigDSData, error) {
+       if tc.CacheTypeFromString(serverInfo.Type) == tc.CacheTypeMid {
+               return GetRemapDSDataForMid(tx, serverInfo)
+       } else {
+               return GetRemapDSDataForEdge(tx, serverInfo)
+       }
+}
+
+const RemapDSDataQuerySelectFrom = `
+SELECT
+  ds.xml_id,
+  ds.id AS ds_id,
+  ds.dscp,
+  ds.routing_name,
+  ds.signing_algorithm,
+  ds.qstring_ignore,
+  (SELECT o.protocol::text || '://' || o.fqdn || rtrim(concat(':', 
o.port::text), ':')
+    FROM origin o
+    WHERE o.deliveryservice = ds.id
+    AND o.is_primary) as org_server_fqdn,
+  ds.multi_site_origin,
+  ds.range_request_handling,
+  ds.fq_pacing_rate,
+  ds.origin_shield,
+  r.pattern,
+  retype.name AS re_type,
+  dstype.name AS ds_type,
+  cdn.domain_name AS domain_name,
+  dsr.set_number,
+  ds.edge_header_rewrite,
+  ds.mid_header_rewrite,
+  ds.regex_remap,
+  ds.cacheurl,
+  ds.remap_text,
+  ds.protocol,
+  ds.profile,
+  ds.anonymous_blocking_enabled,
+  ds.active
+FROM
+  deliveryservice ds
+  JOIN deliveryservice_regex dsr ON dsr.deliveryservice = ds.id
+  JOIN regex r ON dsr.regex = r.id
+  JOIN type retype ON r.type = retype.id
+  JOIN type dstype ON ds.type = dstype.id
+  JOIN cdn ON cdn.id = ds.cdn_id
+`
+
+const RemapDSDataQueryWhereForMid = `
+WHERE
+  cdn.name = $1
+  AND ds.id in (SELECT dss.deliveryservice FROM deliveryservice_server dss)
+  AND ds.active = true
+`
+
+const RemapDSDataQueryWhereForEdge = `
+JOIN deliveryservice_server dss ON dss.deliveryservice = ds.id
+WHERE dss.server = $1
+`
+
+const RemapDSDataQueryOrderBy = `
+ORDER BY
+  ds_id,
+  re_type,
+  set_number
+`
+
+func GetRemapDSDataForMid(tx *sql.Tx, serverInfo *atscfg.ServerInfo) 
([]atscfg.RemapConfigDSData, error) {
+       qry := RemapDSDataQuerySelectFrom + RemapDSDataQueryWhereForMid + 
RemapDSDataQueryOrderBy
+       rows, err := tx.Query(qry, serverInfo.CDN)
+       if err != nil {
+               return nil, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       dses := []atscfg.RemapConfigDSData{}
+       for rows.Next() {
+               d := atscfg.RemapConfigDSData{}
+               if err := rows.Scan(&d.Name, &d.ID, &d.DSCP, &d.RoutingName, 
&d.SigningAlgorithm, &d.QStringIgnore, &d.OriginFQDN, &d.MultiSiteOrigin, 
&d.RangeRequestHandling, &d.FQPacingRate, &d.OriginShield, &d.Pattern, 
&d.RegexType, &d.Type, &d.Domain, &d.RegexSetNumber, &d.EdgeHeaderRewrite, 
&d.MidHeaderRewrite, &d.RegexRemap, &d.CacheURL, &d.RemapText, &d.Protocol, 
&d.ProfileID, &d.AnonymousBlockingEnabled, &d.Active); err != nil {
+                       return nil, errors.New("scanning: " + err.Error())
+               }
+               if !RemapDotConfigIncludeInactiveDeliveryServices && !d.Active {
+                       continue
+               }
+               d.Type = tc.DSTypeFromString(string(d.Type))
+               dses = append(dses, d)
+       }
+       return dses, nil
+}
+
+func GetRemapDSDataForEdge(tx *sql.Tx, server *atscfg.ServerInfo) 
([]atscfg.RemapConfigDSData, error) {
+       qry := RemapDSDataQuerySelectFrom + RemapDSDataQueryWhereForEdge + 
RemapDSDataQueryOrderBy
+       rows, err := tx.Query(qry, server.ID)
+       if err != nil {
+               return nil, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       dses := []atscfg.RemapConfigDSData{}
+       for rows.Next() {
+               d := atscfg.RemapConfigDSData{}
+               if err := rows.Scan(&d.Name, &d.ID, &d.DSCP, &d.RoutingName, 
&d.SigningAlgorithm, &d.QStringIgnore, &d.OriginFQDN, &d.MultiSiteOrigin, 
&d.RangeRequestHandling, &d.FQPacingRate, &d.OriginShield, &d.Pattern, 
&d.RegexType, &d.Type, &d.Domain, &d.RegexSetNumber, &d.EdgeHeaderRewrite, 
&d.MidHeaderRewrite, &d.RegexRemap, &d.CacheURL, &d.RemapText, &d.Protocol, 
&d.ProfileID, &d.AnonymousBlockingEnabled, &d.Active); err != nil {
+                       return nil, errors.New("scanning: " + err.Error())
+               }
+               if !RemapDotConfigIncludeInactiveDeliveryServices && !d.Active {
+                       continue
+               }
+               d.Type = tc.DSTypeFromString(string(d.Type))
+               dses = append(dses, d)
+       }
+       return dses, nil
+}
+
+func GetServerNameFromID(tx *sql.Tx, id int) (tc.CacheName, bool, error) {
+       qry := `SELECT s.host_name FROM server s WHERE s.id = $1`
+       name := tc.CacheName("")
+       if err := tx.QueryRow(qry, id).Scan(&name); err != nil {
+               if err == sql.ErrNoRows {
+                       return "", false, nil
+               }
+               return "", false, errors.New("querying: " + err.Error())
+       }
+       return name, true, nil
+}
+
+// GetServerNameFromNameOrID returns the server name from a parameter which 
may be the name or ID.
+// This also checks and verifies the existence of the given server, and 
returns an appropriate user error if it doesn't exist.
+// Returns the name, any user error, any system error, and any error code.
+func GetServerNameFromNameOrID(tx *sql.Tx, serverNameOrID string) 
(tc.CacheName, error, error, int) {
+       if serverID, err := strconv.Atoi(serverNameOrID); err == nil {
+               serverName, ok, err := dbhelpers.GetServerNameFromID(tx, 
int64(serverID))
+               if err != nil {
+                       return "", nil, fmt.Errorf("getting server name from id 
%v: %v", serverID, err), http.StatusInternalServerError
+               } else if !ok {
+                       return "", errors.New("server not found"), nil, 
http.StatusNotFound
+               }
+               return tc.CacheName(serverName), nil, nil, http.StatusOK
+       }
+
+       serverName := tc.CacheName(serverNameOrID)
+       if ok, err := dbhelpers.ServerExists(string(serverName), tx); err != 
nil {
+               return "", nil, fmt.Errorf("checking server name '%v' 
existence: %v", serverName, err), http.StatusInternalServerError
+       } else if !ok {
+               return "", errors.New("server not found"), nil, 
http.StatusNotFound
+       }
+       return serverName, nil, nil, http.StatusOK
+}
+
+// GetServerInfoByID returns the necessary info about the server, whether the 
server exists, and any error.
+func GetServerInfoByID(tx *sql.Tx, id int) (*atscfg.ServerInfo, bool, error) {
+       return getServerInfo(tx, ServerInfoQuery()+`WHERE s.id = $1`, 
[]interface{}{id})
+}
+
+// GetServerInfoByHost returns the necessary info about the server, whether 
the server exists, and any error.
+func GetServerInfoByHost(tx *sql.Tx, host tc.CacheName) (*atscfg.ServerInfo, 
bool, error) {
+       return getServerInfo(tx, ServerInfoQuery()+` WHERE s.host_name = $1 `, 
[]interface{}{host})
+}
+
+// getServerInfo returns the necessary info about the server, whether the 
server exists, and any error.
+func getServerInfo(tx *sql.Tx, qry string, qryParams []interface{}) 
(*atscfg.ServerInfo, bool, error) {
+       s := atscfg.ServerInfo{}
+       if err := tx.QueryRow(qry, qryParams...).Scan(&s.CDN, &s.CDNID, &s.ID, 
&s.HostName, &s.DomainName, &s.IP, &s.ProfileID, &s.ProfileName, &s.Port, 
&s.HTTPSPort, &s.Type, &s.CacheGroupID, &s.ParentCacheGroupID, 
&s.SecondaryParentCacheGroupID, &s.ParentCacheGroupType, 
&s.SecondaryParentCacheGroupType); err != nil {
+               if err == sql.ErrNoRows {
+                       return nil, false, nil
+               }
+               return nil, false, errors.New("querying server info: " + 
err.Error())
+       }
+       return &s, true, nil
+}
+
+func ServerInfoQuery() string {
+       return `
+SELECT
+  c.name as cdn,
+  s.cdn_id,
+  s.id,
+  s.host_name,
+  c.domain_name,
+  s.ip_address,
+  s.profile AS profile_id,
+  p.name AS profile_name,
+  s.tcp_port,
+  s.https_port,
+  t.name as type,
+  s.cachegroup,
+  COALESCE(cg.parent_cachegroup_id, ` + strconv.Itoa(atscfg.InvalidID) + `) as 
parent_cachegroup_id,
+  COALESCE(cg.secondary_parent_cachegroup_id, ` + 
strconv.Itoa(atscfg.InvalidID) + `) as secondary_parent_cachegroup_id,
+  COALESCE(parentt.name, '') as parent_cachegroup_type,
+  COALESCE(sparentt.name, '') as secondary_parent_cachegroup_type
+FROM
+  server s
+  JOIN cdn c ON s.cdn_id = c.id
+  JOIN type t ON s.type = t.id
+  JOIN profile p ON p.id = s.profile
+  JOIN cachegroup cg on s.cachegroup = cg.id
+  LEFT JOIN type parentt on parentt.id = (select type from cachegroup where id 
= cg.parent_cachegroup_id)
+  LEFT JOIN type sparentt on sparentt.id = (select type from cachegroup where 
id = cg.secondary_parent_cachegroup_id)
+`
+}
+
+// GetATSMajorVersion returns the major version of the given profile's package 
trafficserver parameter.
+// If no parameter exists, this does not return an error, but rather logs a 
warning and uses DefaultATSVersion.
+func GetATSMajorVersion(tx *sql.Tx, serverProfileID atscfg.ProfileID) (int, 
error) {
+       atsVersion, _, err := GetProfileParamValue(tx, int(serverProfileID), 
"package", "trafficserver")
+       if err != nil {
+               return 0, errors.New("getting profile param value: " + 
err.Error())
+       }
+       if len(atsVersion) == 0 {
+               atsVersion = atscfg.DefaultATSVersion
+               log.Warnln("Parameter package.trafficserver missing for profile 
" + strconv.Itoa(int(serverProfileID)) + ". Assuming version " + atsVersion)
+       }
+       atsMajorVer, err := atscfg.GetATSMajorVersionFromATSVersion(atsVersion)
+       if err != nil {
+               return 0, errors.New("ats version parameter '" + atsVersion + 
"' on this profile is not a number (config_file 'package', name 
'trafficserver')")
+       }
+       return atsMajorVer, nil
+}
+
+// GetTMParams returns the global "tm.url" and "tm.rev_proxy.url" parameters, 
and any error. If either param doesn't exist, an empty string is returned 
without error.
+func GetTMParams(tx *sql.Tx) (TMParams, error) {
+       rows, err := tx.Query(`SELECT name, value from parameter where 
config_file = 'global' AND (name = 'tm.url' OR name = 'tm.rev_proxy.url')`)
+       if err != nil {
+               return TMParams{}, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       p := TMParams{}
+       for rows.Next() {
+               name := ""
+               val := ""
+               if err := rows.Scan(&name, &val); err != nil {
+                       return TMParams{}, errors.New("scanning: " + 
err.Error())
+               }
+               if name == "tm.url" {
+                       p.URL = val
+               } else if name == "tm.rev_proxy.url" {
+                       p.ReverseProxyURL = val
+               } else {
+                       return TMParams{}, errors.New("querying got unexpected 
parameter: " + name + " (value: '" + val + "')") // should never happen
+               }
+       }
+       return p, nil
+}
+
+// GetLocationParams returns a map[configFile]locationParams, and any error. 
If either param doesn't exist, an empty string is returned without error.
+func GetLocationParams(tx *sql.Tx, profileID int) 
(map[string]ConfigProfileParams, error) {
+       qry := `
+SELECT
+  p.name,
+  p.config_file,
+  p.value
+FROM
+  parameter p
+  JOIN profile_parameter pp ON pp.parameter = p.id
+WHERE
+  pp.profile = $1
+`
+       rows, err := tx.Query(qry, profileID)
+       if err != nil {
+               return nil, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       params := map[string]ConfigProfileParams{}
+       for rows.Next() {
+               name := ""
+               file := ""
+               val := ""
+               if err := rows.Scan(&name, &file, &val); err != nil {
+                       return nil, errors.New("scanning: " + err.Error())
+               }
+               if name == "location" {
+                       p := params[file]
+                       p.FileNameOnDisk = file
+                       p.Location = val
+                       params[file] = p
+               } else if name == "URL" {
+                       p := params[file]
+                       p.URL = val
+                       params[file] = p
+               }
+       }
+       return params, nil
+}
+
+// GetServerURISignedDSes returns a list of delivery service names which have 
the given server assigned and have URI signing enabled, and any error.
+func GetServerURISignedDSes(tx *sql.Tx, serverHostName string, serverPort int) 
([]tc.DeliveryServiceName, error) {
+       qry := `
+SELECT
+  ds.xml_id
+FROM
+  deliveryservice ds
+  JOIN deliveryservice_server dss ON ds.id = dss.deliveryservice
+  JOIN server s ON s.id = dss.server
+WHERE
+  s.host_name = $1
+  AND s.tcp_port = $2
+  AND ds.signing_algorithm = 'uri_signing'
+`
+       rows, err := tx.Query(qry, serverHostName, serverPort)
+       if err != nil {
+               return nil, errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+
+       dses := []tc.DeliveryServiceName{}
+       for rows.Next() {
+               ds := tc.DeliveryServiceName("")
+               if err := rows.Scan(&ds); err != nil {
+                       return nil, errors.New("scanning: " + err.Error())
+               }
+               dses = append(dses, ds)
+       }
+       return dses, nil
+}
+
+type TMParams struct {
+       URL             string
+       ReverseProxyURL string
+}
+
+type ConfigProfileParams struct {
+       FileNameOnDisk string
+       Location       string
+       URL            string
+       APIURI         string
+}
+
+// GetFirstScopeParameter returns the value of the arbitrarily-first parameter 
with the name 'scope' and the given config file, whether a parameter was found, 
and any error.
+func GetFirstScopeParameter(tx *sql.Tx, cfgFile string) (string, bool, error) {
+       v := ""
+       if err := tx.QueryRow(`SELECT p.value FROM parameter p WHERE 
p.config_file = $1 AND p.name = 'scope'`, cfgFile).Scan(&v); err != nil {
+               if err == sql.ErrNoRows {
+                       return "", false, nil
+               }
+               return "", false, errors.New("querying first scope parameter: " 
+ err.Error())
+       }
+       return v, true, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 0de74d2..9dd1403 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -38,6 +38,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/asn"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats"
        
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats/atsprofile"
+       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats/atsserver"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
        
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cachegroup"
        
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cachegroupparameter"
@@ -389,7 +390,6 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                {1.1, http.MethodPut, `snapshot/{cdn}/?$`, 
crconfig.SnapshotHandler, auth.PrivLevelOperations, Authenticated, nil},
 
                // ATS config files
-               {1.1, http.MethodGet, 
`servers/{server-name-or-id}/configfiles/ats/?(\.json)?$`, 
ats.GetConfigMetaData, auth.PrivLevelOperations, Authenticated, nil},
                {1.1, http.MethodGet, 
`cdns/{cdn-name-or-id}/configfiles/ats/regex_revalidate.config/?(\.json)?$`, 
ats.GetRegexRevalidateDotConfig, auth.PrivLevelOperations, Authenticated, nil},
                {1.1, http.MethodGet, 
`cdns/{cdn-name-or-id}/configfiles/ats/hdr_rw_mid_{xml-id}.config/?(\.json)?$`, 
ats.GetMidHeaderRewriteDotConfig, auth.PrivLevelOperations, Authenticated, nil},
                {1.1, http.MethodGet, 
`cdns/{cdn-name-or-id}/configfiles/ats/hdr_rw_{xml-id}.config/?(\.json)?$`, 
ats.GetEdgeHeaderRewriteDotConfig, auth.PrivLevelOperations, Authenticated, 
nil},
@@ -411,8 +411,9 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                {1.1, http.MethodGet, 
`profiles/{profile-name-or-id}/configfiles/ats/volume.config/?$`, 
atsprofile.GetVolume, auth.PrivLevelOperations, Authenticated, nil},
                {1.1, http.MethodGet, 
`profiles/{profile-name-or-id}/configfiles/ats/{file}/?$`, 
atsprofile.GetUnknown, auth.PrivLevelOperations, Authenticated, nil},
 
-               // Cache Configs
-               {1.1, http.MethodGet, 
`servers/{id-or-host}/configfiles/ats/parent.config/?(\.json)?$`, 
ats.GetParentDotConfig, auth.PrivLevelOperations, Authenticated, nil},
+               {1.1, http.MethodGet, 
`servers/{server-name-or-id}/configfiles/ats/?(\.json)?$`, 
atsserver.GetConfigMetaData, auth.PrivLevelOperations, Authenticated, nil},
+               {1.1, http.MethodGet, 
`servers/{server-name-or-id}/configfiles/ats/parent.config/?(\.json)?$`, 
atsserver.GetParentDotConfig, auth.PrivLevelOperations, Authenticated, nil},
+               {1.1, http.MethodGet, 
`servers/{server-name-or-id}/configfiles/ats/remap.config/?(\.json)?$`, 
atsserver.GetServerConfigRemap, auth.PrivLevelOperations, Authenticated, nil},
 
                // Federations
                {1.4, http.MethodGet, `federations/all/?(\.json)?$`, 
federations.GetAll, auth.PrivLevelAdmin, Authenticated, nil},

Reply via email to