rawlinp commented on a change in pull request #4790:
URL: https://github.com/apache/trafficcontrol/pull/4790#discussion_r445175362



##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -162,6 +165,24 @@ type OriginURI struct {
        Port   string
 }
 
+/*

Review comment:
       I imagine this comment was helpful for developing this PR but probably 
isn't needed anymore.

##########
File path: lib/go-atscfg/hostingdotconfig.go
##########
@@ -74,3 +92,22 @@ func MakeHostingDotConfig(
        text += strings.Join(lines, "")
        return text
 }
+
+func hostingMakeDSTopologies(dses []tc.DeliveryServiceNullable, topologies 
[]tc.Topology) map[tc.DeliveryServiceName]tc.Topology {

Review comment:
       This function appears to basically duplicate the `makeDSTopologies` 
function in remapdotconfig.go except for the `== nil` checks and using 
`tc.DeliveryServiceNullable`. Can they be consolidated and shared?

##########
File path: lib/go-atscfg/atscfg.go
##########
@@ -134,3 +136,64 @@ const ConfigSuffix = ".config"
 func GetConfigFile(prefix string, xmlId string) string {
        return prefix + xmlId + ConfigSuffix
 }
+
+// topologyIncludesServer returns whether the given topology includes the 
given server
+func topologyIncludesServer(topology tc.Topology, server tc.Server) bool {
+       _, inTopology := serverTopologyTier(server, topology)
+       return inTopology
+}
+
+// serverTopologyTier returns whether the server is the last tier in the 
topology, and whether the server is in the topology at all.
+func serverTopologyTier(server tc.Server, topology tc.Topology) (bool, bool) {

Review comment:
       With first/inner/last header rewrite now it might make sense for this 
function to specify which of those 3 buckets the server is in and use this when 
generating those configs.

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"
+       txt += "\n"
+       return txt, nil
+}
+
+func getTopologyParentIsProxyStr(serverIsLastTier bool) string {
+       if serverIsLastTier { // && ds.MultiSiteOrigin
+               return ` parent_is_proxy=false`
+       }
+       return ""
+}
+
+func getTopologyRoundRobin(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {
+       roundRobinConsistentHash := "consistent_hash"
+       if !serverIsLastTier {
+               return roundRobinConsistentHash
+       }
+       if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; 
ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
+               return parentSelectAlg
+       }
+       if ds.MultiSiteOrigin {
+               return ds.MSOAlgorithm
+       }
+       return roundRobinConsistentHash
+}
+
+func getTopologyGoDirect(server tc.Server, ds ParentConfigDSTopLevel, topology 
tc.Topology, serverIsLastTier bool) string {
+       if !serverIsLastTier {
+               // TODO make sure this is correct for DSTypeHTTPNoCache || 
DSTypeHTTPLive || DSTypeDNSLive
+               return "false"
+       }
+       if ds.OriginShield != "" {
+               return "true"
+       }
+       if ds.MultiSiteOrigin {
+               return "false"
+       }
+       // TODO make sure this is correct. If we're the last tier, we should go 
direct to the origin, right?
+       return "true"
+}
+
+func getTopologyQueryString(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {

Review comment:
       `server` and `topology` are unused

##########
File path: lib/go-atscfg/atscfg.go
##########
@@ -134,3 +136,64 @@ const ConfigSuffix = ".config"
 func GetConfigFile(prefix string, xmlId string) string {
        return prefix + xmlId + ConfigSuffix
 }
+
+// topologyIncludesServer returns whether the given topology includes the 
given server
+func topologyIncludesServer(topology tc.Topology, server tc.Server) bool {
+       _, inTopology := serverTopologyTier(server, topology)
+       return inTopology
+}
+
+// serverTopologyTier returns whether the server is the last tier in the 
topology, and whether the server is in the topology at all.
+func serverTopologyTier(server tc.Server, topology tc.Topology) (bool, bool) {
+       serverNode := tc.TopologyNode{}
+       for _, node := range topology.Nodes {
+               if node.Cachegroup == server.Cachegroup {
+                       serverNode = node
+                       break
+               }
+       }
+       if serverNode.Cachegroup == "" {
+               return false, false
+       }
+
+       return len(serverNode.Parents) == 0, true

Review comment:
       So for MSO purposes I think we may need to include `ORG_LOC` cachegroups 
in topologies. So a more valid check for this is probably that there are no 
parents _or_ the parents are all `ORG_LOC`.

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"
+       txt += "\n"
+       return txt, nil
+}
+
+func getTopologyParentIsProxyStr(serverIsLastTier bool) string {
+       if serverIsLastTier { // && ds.MultiSiteOrigin
+               return ` parent_is_proxy=false`
+       }
+       return ""
+}
+
+func getTopologyRoundRobin(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {
+       roundRobinConsistentHash := "consistent_hash"
+       if !serverIsLastTier {
+               return roundRobinConsistentHash
+       }
+       if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; 
ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
+               return parentSelectAlg
+       }
+       if ds.MultiSiteOrigin {
+               return ds.MSOAlgorithm
+       }
+       return roundRobinConsistentHash
+}
+
+func getTopologyGoDirect(server tc.Server, ds ParentConfigDSTopLevel, topology 
tc.Topology, serverIsLastTier bool) string {
+       if !serverIsLastTier {
+               // TODO make sure this is correct for DSTypeHTTPNoCache || 
DSTypeHTTPLive || DSTypeDNSLive
+               return "false"
+       }
+       if ds.OriginShield != "" {
+               return "true"
+       }
+       if ds.MultiSiteOrigin {
+               return "false"
+       }
+       // TODO make sure this is correct. If we're the last tier, we should go 
direct to the origin, right?

Review comment:
       This seems right to me... 🤔 although, if we're the last tier and the DS 
doesn't have an origin shield or MSO, I think we normally don't include a 
parent.config line for the DS. So this kind of changes that, right? Every 
topology-based DS will get a parent.config line on last-tier caches now?
   
   As long as ATS parenting behavior is unchanged, I don't really see a problem 
with that.

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"

Review comment:
       Should we include the topology name in this comment as well? Seems like 
that would be pretty helpful

##########
File path: lib/go-atscfg/remapdotconfig.go
##########
@@ -166,14 +188,20 @@ 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,
+       serverInfo *ServerInfo,
        dses []RemapConfigDSData,
        atsMajorVersion int,
        header string,
+       server tc.Server,
+       topologies map[tc.DeliveryServiceName]tc.Topology,
 ) string {
        textLines := []string{}
 
        for _, ds := range dses {
+               topology, hasTopology := 
topologies[tc.DeliveryServiceName(ds.Name)]
+               if hasTopology && !topologyIncludesServer(topology, server) {

Review comment:
       I think this needs to check the required capabilities before generating 
a remap line for a given DS, right?

##########
File path: lib/go-atscfg/hostingdotconfig.go
##########
@@ -74,3 +92,22 @@ func MakeHostingDotConfig(
        text += strings.Join(lines, "")
        return text
 }
+
+func hostingMakeDSTopologies(dses []tc.DeliveryServiceNullable, topologies 
[]tc.Topology) map[tc.DeliveryServiceName]tc.Topology {
+       dsTops := map[tc.DeliveryServiceName]tc.Topology{}
+       topNames := map[string]tc.Topology{}
+       for _, to := range topologies {
+               topNames[to.Name] = to
+       }
+       for _, ds := range dses {
+               if ds.Topology == nil || ds.XMLID == nil {
+                       continue
+               }
+               if to, ok := topNames[*ds.Topology]; ok {
+                       dsTops[tc.DeliveryServiceName(*ds.XMLID)] = to
+               } else if *ds.Topology != "" {
+                       log.Errorln("Making remap.config for Delivery Service 
'" + *ds.XMLID + "': has topology '" + *ds.Topology + "', but that topology 
doesn't exist! Treating as if DS has no Topology!")

Review comment:
       The error message should say `hosting.config`, but if this function is 
consolidated with `makeDSTopologies` maybe it should be left to the caller to 
prepend the `Making *.config` context.

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"
+       txt += "\n"
+       return txt, nil
+}
+
+func getTopologyParentIsProxyStr(serverIsLastTier bool) string {
+       if serverIsLastTier { // && ds.MultiSiteOrigin
+               return ` parent_is_proxy=false`
+       }
+       return ""
+}
+
+func getTopologyRoundRobin(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {
+       roundRobinConsistentHash := "consistent_hash"
+       if !serverIsLastTier {
+               return roundRobinConsistentHash
+       }
+       if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; 
ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
+               return parentSelectAlg
+       }
+       if ds.MultiSiteOrigin {
+               return ds.MSOAlgorithm
+       }
+       return roundRobinConsistentHash
+}
+
+func getTopologyGoDirect(server tc.Server, ds ParentConfigDSTopLevel, topology 
tc.Topology, serverIsLastTier bool) string {
+       if !serverIsLastTier {
+               // TODO make sure this is correct for DSTypeHTTPNoCache || 
DSTypeHTTPLive || DSTypeDNSLive
+               return "false"
+       }
+       if ds.OriginShield != "" {
+               return "true"
+       }
+       if ds.MultiSiteOrigin {
+               return "false"
+       }
+       // TODO make sure this is correct. If we're the last tier, we should go 
direct to the origin, right?
+       return "true"
+}
+
+func getTopologyQueryString(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {
+       if serverIsLastTier {
+               if ds.MultiSiteOrigin && ds.QStringHandling == "" && 
ds.MSOAlgorithm == tc.AlgorithmConsistentHash && ds.QStringIgnore == 
tc.QStringIgnoreUseInCacheKeyAndPassUp {
+                       return "consider"
+               }
+               return "ignore"
+       }
+
+       if param := serverParams[ParentConfigParamQStringHandling]; param != "" 
{
+               return param
+       }
+       if ds.QStringHandling != "" {
+               return ds.QStringHandling
+       }
+       if ds.QStringIgnore == tc.QStringIgnoreUseInCacheKeyAndPassUp {
+               return "consider"
+       }
+       return "ignore"
+}
+
+// serverParentageParams gets the Parameters used for parent= line, or 
defaults if they don't exist
+// Returns the Parameters used for parent= lines, for the given server.
+func serverParentageParams(sv tc.Server, params []ParameterWithProfilesMap) 
ProfileCache {
+       // TODO deduplicate with atstccfg/parentdotconfig.go
+       profileCache := DefaultProfileCache()
+       profileCache.Port = sv.TCPPort
+       for _, param := range params {
+               if _, ok := param.ProfileNames[sv.Profile]; !ok {
+                       continue
+               }
+               switch param.Name {
+               case ParentConfigCacheParamWeight:
+                       profileCache.Weight = param.Value
+               case ParentConfigCacheParamPort:
+                       i, err := strconv.ParseInt(param.Value, 10, 64)
+                       if err != nil {
+                               log.Errorln("parent.config generation: port 
param is not an integer, skipping! : " + err.Error())
+                       } else {
+                               profileCache.Port = int(i)
+                       }
+               case ParentConfigCacheParamUseIP:
+                       profileCache.UseIP = param.Value == "1"
+               case ParentConfigCacheParamRank:
+                       i, err := strconv.ParseInt(param.Value, 10, 64)
+                       if err != nil {
+                               log.Errorln("parent.config generation: rank 
param is not an integer, skipping! : " + err.Error())
+                       } else {
+                               profileCache.Rank = int(i)
+                       }
+               case ParentConfigCacheParamNotAParent:
+                       profileCache.NotAParent = param.Value != "false"
+               }
+       }
+       return profileCache
+}
+
+func serverParentStr(sv tc.Server, params []ParameterWithProfilesMap) string {
+       svParams := serverParentageParams(sv, params)
+       if svParams.NotAParent {
+               return ""
+       }
+       host := ""
+       if svParams.UseIP {
+               host = sv.IPAddress
+       } else {
+               host = sv.HostName + "." + sv.DomainName
+       }
+       return host + ":" + strconv.Itoa(svParams.Port) + "|" + svParams.Weight
+}
+
+func GetTopologyParents(
+       server tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,

Review comment:
       This parameter is unused

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"
+       txt += "\n"
+       return txt, nil
+}
+
+func getTopologyParentIsProxyStr(serverIsLastTier bool) string {
+       if serverIsLastTier { // && ds.MultiSiteOrigin
+               return ` parent_is_proxy=false`
+       }
+       return ""
+}
+
+func getTopologyRoundRobin(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {

Review comment:
       `server` and `topology` are unused

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"
+       txt += "\n"
+       return txt, nil
+}
+
+func getTopologyParentIsProxyStr(serverIsLastTier bool) string {
+       if serverIsLastTier { // && ds.MultiSiteOrigin
+               return ` parent_is_proxy=false`
+       }
+       return ""
+}
+
+func getTopologyRoundRobin(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {
+       roundRobinConsistentHash := "consistent_hash"
+       if !serverIsLastTier {
+               return roundRobinConsistentHash
+       }
+       if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; 
ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
+               return parentSelectAlg
+       }
+       if ds.MultiSiteOrigin {
+               return ds.MSOAlgorithm
+       }
+       return roundRobinConsistentHash
+}
+
+func getTopologyGoDirect(server tc.Server, ds ParentConfigDSTopLevel, topology 
tc.Topology, serverIsLastTier bool) string {
+       if !serverIsLastTier {
+               // TODO make sure this is correct for DSTypeHTTPNoCache || 
DSTypeHTTPLive || DSTypeDNSLive
+               return "false"
+       }
+       if ds.OriginShield != "" {
+               return "true"
+       }
+       if ds.MultiSiteOrigin {
+               return "false"
+       }
+       // TODO make sure this is correct. If we're the last tier, we should go 
direct to the origin, right?
+       return "true"
+}
+
+func getTopologyQueryString(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {
+       if serverIsLastTier {
+               if ds.MultiSiteOrigin && ds.QStringHandling == "" && 
ds.MSOAlgorithm == tc.AlgorithmConsistentHash && ds.QStringIgnore == 
tc.QStringIgnoreUseInCacheKeyAndPassUp {
+                       return "consider"
+               }
+               return "ignore"
+       }
+
+       if param := serverParams[ParentConfigParamQStringHandling]; param != "" 
{
+               return param
+       }
+       if ds.QStringHandling != "" {
+               return ds.QStringHandling
+       }
+       if ds.QStringIgnore == tc.QStringIgnoreUseInCacheKeyAndPassUp {
+               return "consider"
+       }
+       return "ignore"
+}
+
+// serverParentageParams gets the Parameters used for parent= line, or 
defaults if they don't exist
+// Returns the Parameters used for parent= lines, for the given server.
+func serverParentageParams(sv tc.Server, params []ParameterWithProfilesMap) 
ProfileCache {
+       // TODO deduplicate with atstccfg/parentdotconfig.go
+       profileCache := DefaultProfileCache()
+       profileCache.Port = sv.TCPPort
+       for _, param := range params {
+               if _, ok := param.ProfileNames[sv.Profile]; !ok {
+                       continue
+               }
+               switch param.Name {
+               case ParentConfigCacheParamWeight:
+                       profileCache.Weight = param.Value
+               case ParentConfigCacheParamPort:
+                       i, err := strconv.ParseInt(param.Value, 10, 64)
+                       if err != nil {
+                               log.Errorln("parent.config generation: port 
param is not an integer, skipping! : " + err.Error())
+                       } else {
+                               profileCache.Port = int(i)
+                       }
+               case ParentConfigCacheParamUseIP:
+                       profileCache.UseIP = param.Value == "1"
+               case ParentConfigCacheParamRank:
+                       i, err := strconv.ParseInt(param.Value, 10, 64)
+                       if err != nil {
+                               log.Errorln("parent.config generation: rank 
param is not an integer, skipping! : " + err.Error())
+                       } else {
+                               profileCache.Rank = int(i)
+                       }
+               case ParentConfigCacheParamNotAParent:
+                       profileCache.NotAParent = param.Value != "false"
+               }
+       }
+       return profileCache
+}
+
+func serverParentStr(sv tc.Server, params []ParameterWithProfilesMap) string {
+       svParams := serverParentageParams(sv, params)
+       if svParams.NotAParent {
+               return ""
+       }
+       host := ""
+       if svParams.UseIP {
+               host = sv.IPAddress
+       } else {
+               host = sv.HostName + "." + sv.DomainName
+       }
+       return host + ":" + strconv.Itoa(svParams.Port) + "|" + svParams.Weight
+}
+
+func GetTopologyParents(
+       server tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       servers []tc.Server,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.confign
+       topology tc.Topology,
+       serverIsLastTier bool,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) ([]string, []string, error) {
+       // If it's the last tier, then the parent is the origin.
+       // Note this doesn't include MSO, whose final tier cachegroup points to 
the origin cachegroup.
+       if serverIsLastTier {
+               orgURI, err := GetOriginURI(ds.OriginFQDN) // TODO pass, 
instead of calling again
+               if err != nil {
+                       return nil, nil, err
+               }
+               return []string{orgURI.Host}, nil, nil
+       }
+
+       svNode := tc.TopologyNode{}
+       for _, node := range topology.Nodes {
+               if node.Cachegroup == server.Cachegroup {
+                       svNode = node
+                       break
+               }
+       }
+       if svNode.Cachegroup == "" {
+               return nil, nil, errors.New("This server '" + server.HostName + 
"' not in DS " + string(ds.Name) + " topology, skipping")
+       }
+
+       if len(svNode.Parents) == 0 {
+               return nil, nil, errors.New("DS " + string(ds.Name) + " 
topology '" + ds.Topology + "' is last tier, but NonLastTier called! Should 
never happen")
+       }
+       if numParents := len(svNode.Parents); numParents > 2 {
+               log.Errorln("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' has " + strconv.Itoa(numParents) + " parent nodes, but Apache 
Traffic Server only supports Primary and Secondary (2) lists of parents. 
CacheGroup nodes after the first 2 will be ignored!")
+       }
+       if len(topology.Nodes) < svNode.Parents[0] {

Review comment:
       I think this needs to be `<=`

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"
+       txt += "\n"
+       return txt, nil
+}
+
+func getTopologyParentIsProxyStr(serverIsLastTier bool) string {
+       if serverIsLastTier { // && ds.MultiSiteOrigin
+               return ` parent_is_proxy=false`
+       }
+       return ""
+}
+
+func getTopologyRoundRobin(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {
+       roundRobinConsistentHash := "consistent_hash"
+       if !serverIsLastTier {
+               return roundRobinConsistentHash
+       }
+       if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; 
ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
+               return parentSelectAlg
+       }
+       if ds.MultiSiteOrigin {
+               return ds.MSOAlgorithm
+       }
+       return roundRobinConsistentHash
+}
+
+func getTopologyGoDirect(server tc.Server, ds ParentConfigDSTopLevel, topology 
tc.Topology, serverIsLastTier bool) string {
+       if !serverIsLastTier {
+               // TODO make sure this is correct for DSTypeHTTPNoCache || 
DSTypeHTTPLive || DSTypeDNSLive

Review comment:
       This should be correct, because for topology-based DSes, the DS type 
should no longer determine whether or not it skips the mid tier.

##########
File path: lib/go-atscfg/remapdotconfig.go
##########
@@ -74,28 +75,49 @@ func MakeRemapDotConfig(
        serverPackageParamData map[string]string, // map[paramName]paramVal for 
this server, config file 'package'
        serverInfo *ServerInfo, // ServerInfo for this server
        remapDSData []RemapConfigDSData,
+       topologies []tc.Topology,
 ) string {
-       hdr := GenericHeaderComment(string(serverName), toToolName, toURL)
-       text := ""
+       dsTopologies := makeDSTopologies(remapDSData, topologies)
+       hdr := GenericHeaderComment(server.HostName, toToolName, toURL)
        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 GetServerConfigRemapDotConfigForMid(atsMajorVersion, 
dsProfilesCacheKeyConfigParams, serverInfo, remapDSData, hdr, server, 
dsTopologies)
        }
-       return text
+       return GetServerConfigRemapDotConfigForEdge(cacheURLConfigParams, 
dsProfilesCacheKeyConfigParams, serverPackageParamData, serverInfo, 
remapDSData, atsMajorVersion, hdr, server, dsTopologies)
+}
+
+func makeDSTopologies(dses []RemapConfigDSData, topologies []tc.Topology) 
map[tc.DeliveryServiceName]tc.Topology {
+       dsTops := map[tc.DeliveryServiceName]tc.Topology{}
+       topNames := map[string]tc.Topology{}
+       for _, to := range topologies {
+               topNames[to.Name] = to
+       }
+       for _, ds := range dses {
+               if to, ok := topNames[ds.Topology]; ok {
+                       dsTops[tc.DeliveryServiceName(ds.Name)] = to
+               } else if ds.Topology != "" {
+                       log.Errorln("Making remap.config for Delivery Service 
'" + ds.Name + "': has topology '" + ds.Topology + "', but that topology 
doesn't exist! Treating as if DS has no Topology!")
+               }
+       }
+       return dsTops
 }
 
 func GetServerConfigRemapDotConfigForMid(
        atsMajorVersion int,
        profilesCacheKeyConfigParams map[int]map[string]string,
-       server *ServerInfo,
+       serverInfo *ServerInfo,
        dses []RemapConfigDSData,
        header string,
+       server tc.Server,
+       topologies map[tc.DeliveryServiceName]tc.Topology,
 ) string {
        midRemaps := map[string]string{}
        for _, ds := range dses {
-               if ds.Type.IsLive() && !ds.Type.IsNational() {
-                       continue // Live local delivery services skip mids
+               topology, hasTopology := 
topologies[tc.DeliveryServiceName(ds.Name)]
+               if hasTopology && !topologyIncludesServer(topology, server) {

Review comment:
       I think this needs to check the required capabilities before generating 
a remap line for a given DS, right?

##########
File path: lib/go-atscfg/remapdotconfig.go
##########
@@ -196,7 +224,11 @@ func GetServerConfigRemapDotConfigForEdge(
                        if ds.ProfileID != nil {
                                profilecacheKeyConfigParams = 
profilesCacheKeyConfigParams[*ds.ProfileID]
                        }
-                       remapText = BuildRemapLine(cacheURLConfigParams, 
atsMajorVersion, server, serverPackageParamData, remapText, ds, line.From, 
line.To, profilecacheKeyConfigParams)
+                       remapText = BuildRemapLine(cacheURLConfigParams, 
atsMajorVersion, serverInfo, serverPackageParamData, remapText, ds, line.From, 
line.To, profilecacheKeyConfigParams)
+                       if hasTopology {
+                               remapText += " # topology" // debug

Review comment:
       This comment should include the topology name as well

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities

Review comment:
       This TODO seems to be done, but the TODO below doesn't seem to be done 
because remap.config generation doesn't seem to check capabilities yet. Did I 
miss where you're doing that now?

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"
+       txt += "\n"
+       return txt, nil
+}
+
+func getTopologyParentIsProxyStr(serverIsLastTier bool) string {
+       if serverIsLastTier { // && ds.MultiSiteOrigin
+               return ` parent_is_proxy=false`
+       }
+       return ""
+}
+
+func getTopologyRoundRobin(server tc.Server, ds ParentConfigDSTopLevel, 
topology tc.Topology, serverParams map[string]string, serverIsLastTier bool) 
string {
+       roundRobinConsistentHash := "consistent_hash"
+       if !serverIsLastTier {
+               return roundRobinConsistentHash
+       }
+       if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; 
ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
+               return parentSelectAlg
+       }
+       if ds.MultiSiteOrigin {
+               return ds.MSOAlgorithm
+       }
+       return roundRobinConsistentHash
+}
+
+func getTopologyGoDirect(server tc.Server, ds ParentConfigDSTopLevel, topology 
tc.Topology, serverIsLastTier bool) string {

Review comment:
       `server` and `topology` are unused

##########
File path: traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go
##########
@@ -70,7 +70,7 @@ func GetConfigFileCDNHeaderRewrite(toData *config.TOData, 
fileName string) (stri
                if server.CDNName != *tcDS.CDNName {
                        continue
                }
-               if _, ok := dsServerIDs[server.ID]; !ok {
+               if _, ok := dsServerIDs[server.ID]; !ok && tcDS.Topology == nil 
{

Review comment:
       Do we need to take capabilities into account when determining whether or 
not a given header-rewrite should be generated for a cache? I suppose as long 
as the remap is omitted in that case, the header-rewrite configs are just cruft.

##########
File path: lib/go-atscfg/remapdotconfig.go
##########
@@ -74,28 +75,49 @@ func MakeRemapDotConfig(
        serverPackageParamData map[string]string, // map[paramName]paramVal for 
this server, config file 'package'
        serverInfo *ServerInfo, // ServerInfo for this server
        remapDSData []RemapConfigDSData,
+       topologies []tc.Topology,
 ) string {
-       hdr := GenericHeaderComment(string(serverName), toToolName, toURL)
-       text := ""
+       dsTopologies := makeDSTopologies(remapDSData, topologies)
+       hdr := GenericHeaderComment(server.HostName, toToolName, toURL)
        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 GetServerConfigRemapDotConfigForMid(atsMajorVersion, 
dsProfilesCacheKeyConfigParams, serverInfo, remapDSData, hdr, server, 
dsTopologies)
        }
-       return text
+       return GetServerConfigRemapDotConfigForEdge(cacheURLConfigParams, 
dsProfilesCacheKeyConfigParams, serverPackageParamData, serverInfo, 
remapDSData, atsMajorVersion, hdr, server, dsTopologies)
+}
+
+func makeDSTopologies(dses []RemapConfigDSData, topologies []tc.Topology) 
map[tc.DeliveryServiceName]tc.Topology {
+       dsTops := map[tc.DeliveryServiceName]tc.Topology{}
+       topNames := map[string]tc.Topology{}
+       for _, to := range topologies {
+               topNames[to.Name] = to
+       }
+       for _, ds := range dses {
+               if to, ok := topNames[ds.Topology]; ok {
+                       dsTops[tc.DeliveryServiceName(ds.Name)] = to
+               } else if ds.Topology != "" {
+                       log.Errorln("Making remap.config for Delivery Service 
'" + ds.Name + "': has topology '" + ds.Topology + "', but that topology 
doesn't exist! Treating as if DS has no Topology!")
+               }
+       }
+       return dsTops
 }
 
 func GetServerConfigRemapDotConfigForMid(
        atsMajorVersion int,
        profilesCacheKeyConfigParams map[int]map[string]string,
-       server *ServerInfo,
+       serverInfo *ServerInfo,

Review comment:
       `serverInfo` is now unused

##########
File path: lib/go-atscfg/parentdotconfig.go
##########
@@ -331,13 +345,270 @@ func MakeParentDotConfig(
                        defaultDestText += ` qstring=` + qStr
                }
                defaultDestText += "\n"
-
-               sort.Sort(sort.StringSlice(textArr))
-               text = hdr + strings.Join(textArr, "") + defaultDestText
        }
+
+       sort.Sort(sort.StringSlice(textArr))
+       text := hdr + strings.Join(textArr, "") + defaultDestText
        return text
 }
 
+func GetTopologyParentConfigLine(
+       server tc.Server,
+       servers []tc.Server,
+       ds ParentConfigDSTopLevel,
+       serverParams map[string]string,
+       parentConfigParams []ParameterWithProfilesMap, // all params with 
configFile parent.config
+       topologies []tc.Topology,
+       serverCapabilities map[int]map[ServerCapability]struct{},
+) (string, error) {
+       txt := ""
+
+       if !HasRequiredCapabilities(serverCapabilities[server.ID], 
ds.RequiredCapabilities) {
+               return "", nil
+       }
+
+       orgURI, err := GetOriginURI(ds.OriginFQDN)
+       if err != nil {
+               return "", errors.New("Malformed ds '" + string(ds.Name) + "' 
origin  URI: '" + ds.OriginFQDN + "': skipping!" + err.Error())
+       }
+
+       // This could be put in a map beforehand to only iterate once, if 
performance mattered
+       topology := tc.Topology{}
+       for _, to := range topologies {
+               if to.Name == ds.Topology {
+                       topology = to
+                       break
+               }
+       }
+       if topology.Name == "" {
+               return "", errors.New("DS " + string(ds.Name) + " topology '" + 
ds.Topology + "' not found in Topologies!")
+       }
+
+       txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
+
+       serverIsLastTier, serverInTopology := serverTopologyTier(server, 
topology)
+       if !serverInTopology {
+               return "", nil // server isn't in topology, no error
+       }
+       // TODO omit server and parents without necessary Capabilities
+       // TODO add Topology/Capabilities to remap.config
+
+       parents, secondaryParents, err := GetTopologyParents(server, ds, 
serverParams, servers, parentConfigParams, topology, serverIsLastTier, 
serverCapabilities)
+       if err != nil {
+               return "", errors.New("getting topology parents for '" + 
string(ds.Name) + "': skipping! " + err.Error())
+       }
+       txt += ` parent="` + strings.Join(parents, `;`) + `"`
+       if len(secondaryParents) > 0 {
+               txt += ` secondary_parent="` + strings.Join(secondaryParents, 
`;`) + `"`
+       }
+       txt += ` round_robin=` + getTopologyRoundRobin(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += ` go_direct=` + getTopologyGoDirect(server, ds, topology, 
serverIsLastTier)
+       txt += ` qstring=` + getTopologyQueryString(server, ds, topology, 
serverParams, serverIsLastTier)
+       txt += getTopologyParentIsProxyStr(serverIsLastTier)
+       txt += " # topology"
+       txt += "\n"
+       return txt, nil
+}
+
+func getTopologyParentIsProxyStr(serverIsLastTier bool) string {
+       if serverIsLastTier { // && ds.MultiSiteOrigin

Review comment:
       this comment is confusing




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to