blob: b57a920720c7831824881896c47805430923dae7 [file] [log] [blame]
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 (
"bytes"
"errors"
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
)
const ContentTypeParentDotConfig = ContentTypeTextASCII
const LineCommentParentDotConfig = LineCommentHash
const ParentConfigFileName = "parent.config"
const ParentConfigParamQStringHandling = "psel.qstring_handling"
const ParentConfigParamMSOAlgorithm = "mso.algorithm"
const ParentConfigParamMSOParentRetry = "mso.parent_retry"
const ParentConfigParamMSOUnavailableServerRetryResponses = "mso.unavailable_server_retry_responses"
const ParentConfigParamMSOMaxSimpleRetries = "mso.max_simple_retries"
const ParentConfigParamMSOMaxUnavailableServerRetries = "mso.max_unavailable_server_retries"
const ParentConfigParamMergeGroups = "merge_parent_groups"
const ParentConfigParamAlgorithm = "algorithm"
const ParentConfigParamQString = "qstring"
const ParentConfigParamSecondaryMode = "try_all_primaries_before_secondary"
const ParentConfigParamParentRetry = "parent_retry"
const ParentConfigParamUnavailableServerRetryResponses = "unavailable_server_retry_responses"
const ParentConfigParamMaxSimpleRetries = "max_simple_retries"
const ParentConfigParamMaxUnavailableServerRetries = "max_unavailable_server_retries"
const ParentConfigDSParamDefaultMSOAlgorithm = ParentAbstractionServiceRetryPolicyConsistentHash
const ParentConfigDSParamDefaultMSOParentRetry = "both"
const ParentConfigDSParamDefaultMSOUnavailableServerRetryResponses = ""
const ParentConfigDSParamDefaultMaxSimpleRetries = 1
const ParentConfigDSParamDefaultMaxUnavailableServerRetries = 1
const ParentConfigCacheParamWeight = "weight"
const ParentConfigCacheParamPort = "port"
const ParentConfigCacheParamUseIP = "use_ip_address"
const ParentConfigCacheParamRank = "rank"
const ParentConfigCacheParamNotAParent = "not_a_parent"
const StrategyConfigUsePeering = "use_peering"
type OriginHost string
type OriginFQDN string
// ParentConfigOpts contains settings to configure parent.config generation options.
type ParentConfigOpts struct {
// AddComments is whether to add informative comments to the generated file, about what was generated and why.
// Note this does not include the header comment, which is configured separately with HdrComment.
// These comments are human-readable and not guarnateed to be consistent between versions. Automating anything based on them is strongly discouraged.
AddComments bool
// HdrComment is the header comment to include at the beginning of the file.
// This should be the text desired, without comment syntax (like # or //). The file's comment syntax will be added.
// To omit the header comment, pass the empty string.
HdrComment string
}
func MakeParentDotConfig(
dses []DeliveryService,
server *Server,
servers []Server,
topologies []tc.Topology,
tcServerParams []tc.Parameter,
tcParentConfigParams []tc.Parameter,
serverCapabilities map[int]map[ServerCapability]struct{},
dsRequiredCapabilities map[int]map[ServerCapability]struct{},
cacheGroupArr []tc.CacheGroupNullable,
dss []DeliveryServiceServer,
cdn *tc.CDN,
opt *ParentConfigOpts,
) (Cfg, error) {
parentAbstraction, warnings, err := makeParentDotConfigData(
dses,
server,
servers,
topologies,
tcServerParams,
tcParentConfigParams,
serverCapabilities,
dsRequiredCapabilities,
cacheGroupArr,
dss,
cdn,
opt,
)
if err != nil {
return Cfg{}, makeErr(warnings, err.Error())
}
atsMajorVer, verWarns := getATSMajorVersion(tcServerParams)
warnings = append(warnings, verWarns...)
text, paWarns, err := parentAbstractionToParentDotConfig(parentAbstraction, opt, atsMajorVer)
warnings = append(warnings, paWarns...)
if err != nil {
return Cfg{}, makeErr(warnings, err.Error())
}
hdr := ""
if opt.HdrComment != "" {
hdr = makeHdrComment(opt.HdrComment)
}
return Cfg{
Text: hdr + text,
ContentType: ContentTypeParentDotConfig,
LineComment: LineCommentParentDotConfig,
Warnings: warnings,
}, nil
}
func makeParentDotConfigData(
dses []DeliveryService,
server *Server,
servers []Server,
topologies []tc.Topology,
tcServerParams []tc.Parameter,
tcParentConfigParams []tc.Parameter,
serverCapabilities map[int]map[ServerCapability]struct{},
dsRequiredCapabilities map[int]map[ServerCapability]struct{},
cacheGroupArr []tc.CacheGroupNullable,
dss []DeliveryServiceServer,
cdn *tc.CDN,
opt *ParentConfigOpts,
) (*ParentAbstraction, []string, error) {
if opt == nil {
opt = &ParentConfigOpts{}
}
parentAbstraction := &ParentAbstraction{}
warnings := []string{}
if server.HostName == nil || *server.HostName == "" {
return nil, warnings, errors.New("server HostName missing")
} else if server.CDNName == nil || *server.CDNName == "" {
return nil, warnings, errors.New("server CDNName missing")
} else if server.Cachegroup == nil || *server.Cachegroup == "" {
return nil, warnings, errors.New("server Cachegroup missing")
} else if len(server.ProfileNames) == 0 {
return nil, warnings, errors.New("server Profile missing")
} else if server.TCPPort == nil {
return nil, warnings, errors.New("server TCPPort missing")
}
// TODO remove, the abstraction shouldn't depend on the ATS version
atsMajorVer, verWarns := getATSMajorVersion(tcServerParams)
warnings = append(warnings, verWarns...)
cacheGroups, err := makeCGMap(cacheGroupArr)
if err != nil {
return nil, warnings, errors.New("making CacheGroup map: " + err.Error())
}
serverParentCGData, err := getParentCacheGroupData(server, cacheGroups)
if err != nil {
return nil, warnings, errors.New("getting server parent cachegroup data: " + err.Error())
}
cacheIsTopLevel := isTopLevelCache(serverParentCGData)
serverCDNDomain := cdn.DomainName
sort.Sort(dsesSortByName(dses))
profileParentConfigParams, parentWarns := getProfileParentConfigParams(tcParentConfigParams)
warnings = append(warnings, parentWarns...)
parentConfigParamsWithProfiles, err := tcParamsToParamsWithProfiles(tcParentConfigParams)
if err != nil {
return nil, warnings, errors.New("adding profiles to parent config params: " + err.Error())
}
// parentConfigParams are the parent.config params for all profiles (needed for parents)
parentConfigParams := parameterWithProfilesToMap(parentConfigParamsWithProfiles)
serversWithParams := []serverWithParams{}
for _, sv := range servers {
serverParentParams, parentWarns := serverParentageParams(&sv, parentConfigParams)
warnings = append(warnings, parentWarns...)
serversWithParams = append(serversWithParams, serverWithParams{
Server: sv,
Params: serverParentParams,
})
}
sort.Sort(serversWithParamsSortByRank(serversWithParams))
// serverParams are the parent.config params for this particular server
serverParams := getServerParentConfigParams(server, parentConfigParams)
parentCacheGroups := map[string]struct{}{}
if cacheIsTopLevel {
for _, cg := range cacheGroups {
if cg.Type == nil {
return nil, warnings, errors.New("cachegroup type is nil!")
}
if cg.Name == nil {
return nil, warnings, errors.New("cachegroup name is nil!")
}
if *cg.Type != tc.CacheGroupOriginTypeName {
continue
}
parentCacheGroups[*cg.Name] = struct{}{}
}
} else {
for _, cg := range cacheGroups {
if cg.Type == nil {
return nil, warnings, errors.New("cachegroup type is nil!")
}
if cg.Name == nil {
return nil, warnings, errors.New("cachegroup name is nil!")
}
if *cg.Name == *server.Cachegroup {
if cg.ParentName != nil && *cg.ParentName != "" {
parentCacheGroups[*cg.ParentName] = struct{}{}
}
if cg.SecondaryParentName != nil && *cg.SecondaryParentName != "" {
parentCacheGroups[*cg.SecondaryParentName] = struct{}{}
}
break
}
}
}
nameTopologies := makeTopologyNameMap(topologies)
cgPeers := map[int]serverWithParams{} // map[serverID]server
cgServers := map[int]serverWithParams{} // map[serverID]server
for _, sv := range serversWithParams {
if sv.ID == nil {
warnings = append(warnings, "TO servers had server with missing ID, skipping!")
continue
} else if sv.CDNName == nil {
warnings = append(warnings, "TO servers had server with missing CDNName, skipping!")
continue
} else if sv.Cachegroup == nil || *sv.Cachegroup == "" {
warnings = append(warnings, "TO servers had server with missing Cachegroup, skipping!")
continue
} else if sv.Status == nil || *sv.Status == "" {
warnings = append(warnings, "TO servers had server with missing Status, skipping!")
continue
} else if sv.Type == "" {
warnings = append(warnings, "TO servers had server with missing Type, skipping!")
continue
}
if *sv.CDNName != *server.CDNName {
continue
}
// save cachegroup peer servers
if *sv.CDNName == *server.CDNName && *sv.Cachegroup == *server.Cachegroup {
if *sv.Status == string(tc.CacheStatusReported) || *sv.Status == string(tc.CacheStatusOnline) {
if _, ok := cgPeers[*sv.ID]; !ok {
cgPeers[*sv.ID] = sv
}
}
continue
}
if _, ok := parentCacheGroups[*sv.Cachegroup]; !ok {
continue
}
if sv.Type != tc.OriginTypeName &&
!strings.HasPrefix(sv.Type, tc.EdgeTypePrefix) &&
!strings.HasPrefix(sv.Type, tc.MidTypePrefix) {
continue
}
if *sv.Status != string(tc.CacheStatusReported) && *sv.Status != string(tc.CacheStatusOnline) {
continue
}
cgServers[*sv.ID] = sv
}
// save the cache group peers
for _, v := range cgPeers {
peer := &ParentAbstractionServiceParent{}
peer.FQDN = *v.HostName + "." + *v.DomainName
peer.Port = *v.TCPPort
peer.Weight = 0.999
parentAbstraction.Peers = append(parentAbstraction.Peers, peer)
}
cgServerIDs := map[int]struct{}{}
for serverID, _ := range cgServers {
cgServerIDs[serverID] = struct{}{}
}
cgServerIDs[*server.ID] = struct{}{}
cgDSServers := filterDSS(dss, nil, cgServerIDs)
parentServerDSes := map[int]map[int]struct{}{} // map[serverID][dsID]
for _, dss := range cgDSServers {
if parentServerDSes[dss.Server] == nil {
parentServerDSes[dss.Server] = map[int]struct{}{}
}
parentServerDSes[dss.Server][dss.DeliveryService] = struct{}{}
}
originServers, orgProfWarns, err := getOriginServers(cgServers, parentServerDSes, dses, serverCapabilities)
warnings = append(warnings, orgProfWarns...)
if err != nil {
return nil, warnings, errors.New("getting origin servers and profile caches: " + err.Error())
}
parentInfos, piWarns := makeParentInfo(serverParentCGData, serverCDNDomain, originServers, serverCapabilities)
warnings = append(warnings, piWarns...)
dsOrigins, dsOriginWarns := makeDSOrigins(dss, dses, servers)
warnings = append(warnings, dsOriginWarns...)
for _, ds := range dses {
if ds.XMLID == nil || *ds.XMLID == "" {
warnings = append(warnings, "got ds with missing XMLID, skipping!")
continue
} else if ds.ID == nil {
warnings = append(warnings, "got ds with missing ID, skipping!")
continue
} else if ds.Type == nil {
warnings = append(warnings, "got ds with missing Type, skipping!")
continue
}
if !cacheIsTopLevel && ds.Topology == nil {
if _, ok := parentServerDSes[*server.ID][*ds.ID]; !ok {
continue // skip DSes not assigned to this server.
}
}
if !ds.Type.IsHTTP() && !ds.Type.IsDNS() {
continue // skip ANY_MAP, STEERING, etc
}
if ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" {
// this check needs to be after the HTTP|DNS check, because Steering DSes without origins are ok'
warnings = append(warnings, "DS '"+*ds.XMLID+"' has no origin server! Skipping!")
continue
}
// Note these Parameters are only used for MSO for legacy DeliveryServiceServers DeliveryServices (except QueryStringHandling which is used by all DeliveryServices).
// Topology DSes use them for all DSes, MSO and non-MSO.
dsParams, dsParamsWarnings := getParentDSParams(ds, profileParentConfigParams)
warnings = append(warnings, dsParamsWarnings...)
// TODO put these in separate functions. No if-statement should be this long.
if ds.Topology != nil && *ds.Topology != "" {
txt, topoWarnings, err := getTopologyParentConfigLine(
server,
serversWithParams,
&ds,
serverParams,
parentConfigParams,
nameTopologies,
serverCapabilities,
dsRequiredCapabilities,
cacheGroups,
dsParams,
atsMajorVer,
dsOrigins[DeliveryServiceID(*ds.ID)],
opt.AddComments,
)
warnings = append(warnings, topoWarnings...)
if err != nil {
// we don't want to fail generation with an error if one ds is malformed
warnings = append(warnings, err.Error()) // getTopologyParentConfigLine includes error context
continue
}
if txt != nil { // will be nil with no error if this server isn't in the Topology, or if it doesn't have the Required Capabilities
parentAbstraction.Services = append(parentAbstraction.Services, txt)
}
} else if cacheIsTopLevel {
parentQStr := false
if dsParams.QueryStringHandling == "" && dsParams.Algorithm == tc.AlgorithmConsistentHash && ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp {
parentQStr = true
}
orgFQDNStr := *ds.OrgServerFQDN
// if this cache isn't the last tier, i.e. we're not going to the origin, use http not https
if isLastCacheTier := noTopologyServerIsLastCacheForDS(server, &ds); !isLastCacheTier {
orgFQDNStr = strings.Replace(orgFQDNStr, `https://`, `http://`, -1)
}
orgURI, orgWarns, err := getOriginURI(orgFQDNStr)
warnings = append(warnings, orgWarns...)
if err != nil {
warnings = append(warnings, "DS '"+*ds.XMLID+"' has malformed origin URI: '"+orgFQDNStr+"': skipping!"+err.Error())
continue
}
textLine := &ParentAbstractionService{}
textLine.Name = *ds.XMLID
if ds.OriginShield != nil && *ds.OriginShield != "" {
policy := ParentAbstractionServiceRetryPolicyConsistentHash
if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; strings.TrimSpace(parentSelectAlg) != "" {
paramPolicy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(parentSelectAlg)
if paramPolicy != ParentAbstractionServiceRetryPolicyInvalid {
policy = paramPolicy
} else {
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamAlgorithm+" parameter '"+parentSelectAlg+"', not using!")
}
}
textLine.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
textLine.DestDomain = orgURI.Hostname()
textLine.Port, err = strconv.Atoi(orgURI.Port())
if err != nil {
if strings.ToLower(orgURI.Scheme) == "https" {
textLine.Port = 443
} else {
textLine.Port = 80
}
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin port: '"+orgURI.Port()+"': using "+strconv.Itoa(textLine.Port)+"! : "+err.Error())
}
fqdnPort := strings.Split(*ds.OriginShield, ":")
parent := &ParentAbstractionServiceParent{}
parent.FQDN = fqdnPort[0]
if len(fqdnPort) > 1 {
parent.Port, err = strconv.Atoi(fqdnPort[1])
if err != nil {
parent.Port = 80
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin port: '"+*ds.OriginShield+"': using "+strconv.Itoa(parent.Port)+"! : "+err.Error())
}
} else {
parent.Port = 80
warnings = append(warnings, "DS '"+*ds.XMLID+"' had no origin port: '"+*ds.OriginShield+"': using "+strconv.Itoa(parent.Port)+"!")
}
textLine.Parents = append(textLine.Parents, parent)
textLine.RetryPolicy = policy
textLine.GoDirect = true
// textLine += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port() + " parent=" + *ds.OriginShield + " " + algorithm + " go_direct=true\n"
} else if ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin {
textLine.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
textLine.DestDomain = orgURI.Hostname()
textLine.Port, err = strconv.Atoi(orgURI.Port())
if err != nil {
textLine.Port = 80
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin port: '"+orgURI.Port()+"': using "+strconv.Itoa(textLine.Port)+"! : "+err.Error())
}
// textLine += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port() + " "
if len(parentInfos) == 0 {
}
if len(parentInfos[OriginHost(orgURI.Hostname())]) == 0 {
// TODO error? emulates Perl
warnings = append(warnings, "DS "+*ds.XMLID+" has no parent servers")
}
parents, secondaryParents, secondaryMode, parentWarns := getMSOParentStrs(&ds, parentInfos[OriginHost(orgURI.Hostname())], atsMajorVer, dsParams.Algorithm, dsParams.TryAllPrimariesBeforeSecondary)
warnings = append(warnings, parentWarns...)
textLine.Parents = parents
textLine.SecondaryParents = secondaryParents
textLine.SecondaryMode = secondaryMode
textLine.RetryPolicy = dsParams.Algorithm // TODO convert
textLine.IgnoreQueryStringInParentSelection = !parentQStr
textLine.GoDirect = true
// textLine += parents + secondaryParents + ` round_robin=` + dsParams.Algorithm + ` qstring=` + parentQStr + ` go_direct=false parent_is_proxy=false`
prWarns := []string{}
textLine.MaxSimpleRetries, textLine.MaxMarkdownRetries, textLine.MarkdownResponseCodes, textLine.ErrorResponseCodes, prWarns = getParentRetryStr(true, atsMajorVer, dsParams.ParentRetry, dsParams.UnavailableServerRetryResponses, dsParams.MaxSimpleRetries, dsParams.MaxUnavailableServerRetries)
warnings = append(warnings, prWarns...)
parentAbstraction.Services = append(parentAbstraction.Services, textLine)
}
} else {
queryStringHandling := ParentSelectParamQStringHandlingToBool(serverParams[ParentConfigParamQStringHandling]) // "qsh" in Perl
if queryStringHandling == nil && serverParams[ParentConfigParamQStringHandling] != "" {
warnings = append(warnings, "Server Parameter '"+ParentConfigParamQStringHandling+"' value '"+serverParams[ParentConfigParamQStringHandling]+"' malformed, not using!")
}
roundRobin := ParentAbstractionServiceRetryPolicyConsistentHash
// roundRobin := `round_robin=consistent_hash`
goDirect := false
// goDirect := `go_direct=false`
parents, secondaryParents, secondaryMode, parentWarns := getParentStrs(&ds, dsRequiredCapabilities, parentInfos[deliveryServicesAllParentsKey], atsMajorVer, dsParams.TryAllPrimariesBeforeSecondary)
warnings = append(warnings, parentWarns...)
text := &ParentAbstractionService{}
text.Name = *ds.XMLID
// peering ring check
if dsParams.UsePeering {
secondaryMode = ParentAbstractionServiceParentSecondaryModePeering
}
orgFQDNStr := *ds.OrgServerFQDN
// if this cache isn't the last tier, i.e. we're not going to the origin, use http not https
if isLastCacheTier := noTopologyServerIsLastCacheForDS(server, &ds); !isLastCacheTier {
orgFQDNStr = strings.Replace(orgFQDNStr, `https://`, `http://`, -1)
}
orgURI, orgWarns, err := getOriginURI(orgFQDNStr)
warnings = append(warnings, orgWarns...)
if err != nil {
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin URI: '"+*ds.OrgServerFQDN+"': skipping!"+err.Error())
continue
}
text.Comment = makeParentComment(opt.AddComments, *ds.XMLID, "")
// TODO encode this in a DSType func, IsGoDirect() ?
if *ds.Type == tc.DSTypeHTTPNoCache || *ds.Type == tc.DSTypeHTTPLive || *ds.Type == tc.DSTypeDNSLive {
text.DestDomain = orgURI.Hostname()
text.Port, err = strconv.Atoi(orgURI.Port())
if err != nil {
if strings.ToLower(orgURI.Scheme) == "https" {
text.Port = 443
} else {
text.Port = 80
}
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin port: '"+orgURI.Port()+"': using "+strconv.Itoa(text.Port)+"! : "+err.Error())
}
text.GoDirect = true
text.Parents = []*ParentAbstractionServiceParent{&ParentAbstractionServiceParent{
FQDN: text.DestDomain,
Port: text.Port,
Weight: 1.0,
}}
// text += `dest_domain=` + orgURI.Hostname() + ` port=` + orgURI.Port() + ` go_direct=true` + "\n"
} else {
// check for profile psel.qstring_handling. If this parameter is assigned to the server profile,
// then edges will use the qstring handling value specified in the parameter for all profiles.
// If there is no defined parameter in the profile, then check the delivery service profile.
// If psel.qstring_handling exists in the DS profile, then we use that value for the specified DS only.
// This is used only if not overridden by a server profile qstring handling parameter.
// TODO refactor this logic, hard to understand (transliterated from Perl)
dsQSH := queryStringHandling
if dsQSH == nil {
dsQSH = ParentSelectParamQStringHandlingToBool(dsParams.QueryStringHandling)
if dsQSH == nil && dsParams.QueryStringHandling != "" {
warnings = append(warnings, "Delivery Service parameter '"+ParentConfigParamQStringHandling+"' value '"+dsParams.QueryStringHandling+"' malformed, not using!")
}
}
parentQStr := dsQSH
if parentQStr == nil {
v := false
parentQStr = &v
}
if ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp && dsQSH == nil {
v := true
parentQStr = &v
}
if parentQStr == nil {
b := !DefaultIgnoreQueryStringInParentSelection
parentQStr = &b
}
text.DestDomain = orgURI.Hostname()
text.Port, err = strconv.Atoi(orgURI.Port())
if err != nil {
if strings.ToLower(orgURI.Scheme) == "https" {
text.Port = 443
} else {
text.Port = 80
}
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed origin port: '"+orgURI.Port()+"': using "+strconv.Itoa(text.Port)+"! : "+err.Error())
}
text.Parents = parents
text.SecondaryParents = secondaryParents
text.SecondaryMode = secondaryMode
text.RetryPolicy = roundRobin
text.GoDirect = goDirect
text.IgnoreQueryStringInParentSelection = !*parentQStr
// text += `dest_domain=` + orgURI.Hostname() + ` port=` + orgURI.Port() + ` ` + parents + ` ` + secondaryParents + ` ` + roundRobin + ` ` + goDirect + ` qstring=` + parentQStr + "\n"
}
parentAbstraction.Services = append(parentAbstraction.Services, text)
}
}
// TODO determine if this is necessary. It's super-dangerous, and moreover ignores Server Capabilitites.
defaultDestText := (*ParentAbstractionService)(nil)
if !isTopLevelCache(serverParentCGData) {
defaultDestText = &ParentAbstractionService{}
// magic uuid to prevent accidental DS name collision
defaultDestText.Name = `default-destination-c3854be4-a859-41d6-815d-7b36297e48c6`
invalidDS := &DeliveryService{}
invalidDS.ID = util.IntPtr(-1)
tryAllPrimariesBeforeSecondary := false
parents, secondaryParents, secondaryMode, parentWarns := getParentStrs(invalidDS, dsRequiredCapabilities, parentInfos[deliveryServicesAllParentsKey], atsMajorVer, tryAllPrimariesBeforeSecondary)
warnings = append(warnings, parentWarns...)
defaultDestText.DestDomain = `.`
defaultDestText.Parents = parents
// defaultDestText = `dest_domain=. ` + parents
if serverParams[ParentConfigParamAlgorithm] == tc.AlgorithmConsistentHash {
defaultDestText.SecondaryParents = secondaryParents
defaultDestText.SecondaryMode = secondaryMode
// defaultDestText += secondaryParents
}
defaultDestText.RetryPolicy = ParentAbstractionServiceRetryPolicyConsistentHash
defaultDestText.GoDirect = false
// defaultDestText += ` round_robin=consistent_hash go_direct=false`
if qStr := serverParams[ParentConfigParamQString]; qStr != "" {
if v := ParentSelectParamQStringHandlingToBool(qStr); v != nil {
defaultDestText.IgnoreQueryStringInParentSelection = !*v
} else if qStr != "" {
warnings = append(warnings, "Server parameter '"+ParentConfigParamQString+"' value '"+qStr+"' malformed, not using!")
}
// defaultDestText += ` qstring=` + qStr
}
defaultDestText.Comment = makeParentComment(opt.AddComments, "", "")
}
sort.Sort(ParentAbstractionServices(parentAbstraction.Services))
if defaultDestText != nil {
parentAbstraction.Services = append(parentAbstraction.Services, defaultDestText)
}
return parentAbstraction, warnings, nil
}
// makeParentComment creates the parent line comment and returns it.
// If addComments is false, returns the empty string. This exists for composability.
// Either dsName or topology may be the empty string.
// The returned comment includes a trailing newline.
func makeParentComment(addComments bool, dsName string, topology string) string {
if !addComments {
return ""
}
return "ds '" + dsName + "' topology '" + topology + "'"
}
type parentInfo struct {
Host string
Port int
Domain string
Weight float64
UseIP bool
Rank int
IP string
PrimaryParent bool
SecondaryParent bool
Capabilities map[ServerCapability]struct{}
}
func (p parentInfo) Format() string {
host := ""
if p.UseIP {
host = p.IP
} else {
host = p.Host + "." + p.Domain
}
return host + ":" + strconv.Itoa(p.Port) + "|" + strconv.FormatFloat(p.Weight, 'f', 3, 64) + ";"
}
func (p parentInfo) ToAbstract() *ParentAbstractionServiceParent {
host := ""
if p.UseIP {
host = p.IP
} else {
host = p.Host + "." + p.Domain
}
return &ParentAbstractionServiceParent{
FQDN: host,
Port: p.Port,
Weight: p.Weight,
}
}
type parentInfos map[OriginHost]parentInfo
type parentInfoSortByRank []parentInfo
func (s parentInfoSortByRank) Len() int { return len(s) }
func (s parentInfoSortByRank) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s parentInfoSortByRank) Less(i, j int) bool {
if s[i].Rank != s[j].Rank {
return s[i].Rank < s[j].Rank
} else if s[i].Host != s[j].Host {
return s[i].Host < s[j].Host
} else if s[i].Domain != s[j].Domain {
return s[i].Domain < s[j].Domain
} else if s[i].Port != s[j].Port {
return s[i].Port < s[j].Port
}
return s[i].IP < s[j].IP
}
type serverWithParams struct {
Server
Params parentServerParams
}
type serversWithParamsSortByRank []serverWithParams
func (ss serversWithParamsSortByRank) Len() int { return len(ss) }
func (ss serversWithParamsSortByRank) Swap(i, j int) { ss[i], ss[j] = ss[j], ss[i] }
func (ss serversWithParamsSortByRank) Less(i, j int) bool {
if ss[i].Params.Rank != ss[j].Params.Rank {
return ss[i].Params.Rank < ss[j].Params.Rank
}
if ss[i].HostName == nil {
if ss[j].HostName != nil {
return true
}
} else if ss[j].HostName == nil {
return false
} else if ss[i].HostName != ss[j].HostName {
return *ss[i].HostName < *ss[j].HostName
}
if ss[i].DomainName == nil {
if ss[j].DomainName != nil {
return true
}
} else if ss[j].DomainName == nil {
return false
} else if ss[i].DomainName != ss[j].DomainName {
return *ss[i].DomainName < *ss[j].DomainName
}
if ss[i].Params.Port != ss[j].Params.Port {
return ss[i].Params.Port < ss[j].Params.Port
}
iIP := getServerIPAddress(&ss[i].Server)
jIP := getServerIPAddress(&ss[j].Server)
if iIP == nil {
if jIP != nil {
return true
}
} else if jIP == nil {
return false
}
return bytes.Compare(iIP, jIP) <= 0
}
type dsesSortByName []DeliveryService
func (s dsesSortByName) Len() int { return len(s) }
func (s dsesSortByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s dsesSortByName) Less(i, j int) bool {
if s[i].XMLID == nil {
return true
}
if s[j].XMLID == nil {
return false
}
return *s[i].XMLID < *s[j].XMLID
}
type parentServerParams struct {
Weight string
Port int
UseIP bool
Rank int
NotAParent bool
}
const DefaultParentWeight = 0.999
func defaultParentServerParams() parentServerParams {
return parentServerParams{
Weight: strconv.FormatFloat(DefaultParentWeight, 'f', 3, 64),
Port: 0,
UseIP: false,
Rank: 1,
NotAParent: false,
}
}
type originURI struct {
Scheme string
Host string
Port string
}
// 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"
type parentDSParams struct {
Algorithm ParentAbstractionServiceRetryPolicy
ParentRetry string
UnavailableServerRetryResponses string
MaxSimpleRetries string
MaxUnavailableServerRetries string
QueryStringHandling string
TryAllPrimariesBeforeSecondary bool
UsePeering bool
MergeGroups []string
}
// getDSParams returns the Delivery Service Profile Parameters used in parent.config, and any warnings.
// If Parameters don't exist, defaults are returned. Non-MSO Delivery Services default to no custom retry logic (we should reevaluate that).
// Note these Parameters are only used for MSO for legacy DeliveryServiceServers DeliveryServices.
// Topology DSes use them for all DSes, MSO and non-MSO.
func getParentDSParams(ds DeliveryService, profileParentConfigParams map[string]map[string]string) (parentDSParams, []string) {
warnings := []string{}
params := parentDSParams{}
isMSO := ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin
if isMSO {
params.Algorithm = ParentConfigDSParamDefaultMSOAlgorithm
params.ParentRetry = ParentConfigDSParamDefaultMSOParentRetry
params.UnavailableServerRetryResponses = ParentConfigDSParamDefaultMSOUnavailableServerRetryResponses
params.MaxSimpleRetries = strconv.Itoa(ParentConfigDSParamDefaultMaxSimpleRetries)
params.MaxUnavailableServerRetries = strconv.Itoa(ParentConfigDSParamDefaultMaxUnavailableServerRetries)
}
if ds.ProfileName == nil || *ds.ProfileName == "" {
return params, warnings
}
dsParams, ok := profileParentConfigParams[*ds.ProfileName]
if !ok {
return params, warnings
}
if val, ok := dsParams[StrategyConfigUsePeering]; ok {
if val == "true" {
params.UsePeering = true
}
}
// the following may be blank, no default
params.QueryStringHandling = dsParams[ParentConfigParamQStringHandling]
params.MergeGroups = strings.Split(dsParams[ParentConfigParamMergeGroups], " ")
// TODO deprecate & remove "mso." Parameters - there was never a reason to restrict these settings to MSO.
if isMSO {
if v, ok := dsParams[ParentConfigParamMSOAlgorithm]; ok && strings.TrimSpace(v) != "" {
policy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(v)
if policy != ParentAbstractionServiceRetryPolicyInvalid {
params.Algorithm = policy
} else {
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamMSOAlgorithm+" parameter '"+v+"', not using!")
}
}
if v, ok := dsParams[ParentConfigParamMSOParentRetry]; ok {
params.ParentRetry = v
}
if v, ok := dsParams[ParentConfigParamMSOUnavailableServerRetryResponses]; ok {
if v != "" && !unavailableServerRetryResponsesValid(v) {
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamMSOUnavailableServerRetryResponses+" parameter '"+v+"', not using!")
} else if v != "" {
params.UnavailableServerRetryResponses = v
}
}
if v, ok := dsParams[ParentConfigParamMSOMaxSimpleRetries]; ok {
params.MaxSimpleRetries = v
}
if v, ok := dsParams[ParentConfigParamMSOMaxUnavailableServerRetries]; ok {
params.MaxUnavailableServerRetries = v
}
}
// Even if the DS is MSO, non-"mso." Parameters override "mso." ones, because they're newer.
if v, ok := dsParams[ParentConfigParamAlgorithm]; ok && strings.TrimSpace(v) != "" {
policy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(v)
if policy != ParentAbstractionServiceRetryPolicyInvalid {
params.Algorithm = policy
} else {
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamAlgorithm+" parameter '"+v+"', not using!")
}
}
if v, ok := dsParams[ParentConfigParamParentRetry]; ok {
params.ParentRetry = v
}
if v, ok := dsParams[ParentConfigParamUnavailableServerRetryResponses]; ok {
if v != "" && !unavailableServerRetryResponsesValid(v) {
warnings = append(warnings, "DS '"+*ds.XMLID+"' had malformed "+ParentConfigParamUnavailableServerRetryResponses+" parameter '"+v+"', not using!")
} else if v != "" {
params.UnavailableServerRetryResponses = v
}
}
if v, ok := dsParams[ParentConfigParamMaxSimpleRetries]; ok {
params.MaxSimpleRetries = v
}
if v, ok := dsParams[ParentConfigParamMaxUnavailableServerRetries]; ok {
params.MaxUnavailableServerRetries = v
}
if v, ok := dsParams[ParentConfigParamSecondaryMode]; ok {
if v != "" {
warnings = append(warnings, "DS '"+*ds.XMLID+"' had Parameter "+ParentConfigParamSecondaryMode+" which is used if it exists, the value is ignored! Non-empty value '"+v+"' will be ignored!")
}
params.TryAllPrimariesBeforeSecondary = true
}
return params, warnings
}
// getTopologyParentConfigLine returns the topology parent.config line, any warnings, and any error
// If the given DS is not used by the server, returns a nil ParentAbstractionService and nil error.
func getTopologyParentConfigLine(
server *Server,
serversWithParams []serverWithParams,
ds *DeliveryService,
serverParams map[string]string,
parentConfigParams []parameterWithProfilesMap, // all params with configFile parent.config
nameTopologies map[TopologyName]tc.Topology,
serverCapabilities map[int]map[ServerCapability]struct{},
dsRequiredCapabilities map[int]map[ServerCapability]struct{},
cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable,
dsParams parentDSParams,
atsMajorVer int,
dsOrigins map[ServerID]struct{},
addComments bool,
) (*ParentAbstractionService, []string, error) {
warnings := []string{}
if !hasRequiredCapabilities(serverCapabilities[*server.ID], dsRequiredCapabilities[*ds.ID]) {
return nil, warnings, nil
}
topology := nameTopologies[TopologyName(*ds.Topology)]
if topology.Name == "" {
return nil, warnings, errors.New("DS " + *ds.XMLID + " topology '" + *ds.Topology + "' not found in Topologies!")
}
serverPlacement, err := getTopologyPlacement(tc.CacheGroupName(*server.Cachegroup), topology, cacheGroups, ds)
if err != nil {
return nil, warnings, errors.New("getting topology placement: " + err.Error())
}
if !serverPlacement.InTopology {
return nil, warnings, nil // server isn't in topology, no error
}
orgFQDNStr := *ds.OrgServerFQDN
// if this cache isn't the last tier, i.e. we're not going to the origin, use http not https
if !serverPlacement.IsLastCacheTier {
orgFQDNStr = strings.Replace(orgFQDNStr, `https://`, `http://`, -1)
}
orgURI, orgWarns, err := getOriginURI(orgFQDNStr)
warnings = append(warnings, orgWarns...)
if err != nil {
return nil, warnings, errors.New("DS '" + *ds.XMLID + "' has malformed origin URI: '" + *ds.OrgServerFQDN + "': skipping!" + err.Error())
}
txt := &ParentAbstractionService{}
txt.Name = *ds.XMLID
txt.Comment = makeParentComment(addComments, *ds.XMLID, *ds.Topology)
txt.DestDomain = orgURI.Hostname()
txt.Port, err = strconv.Atoi(orgURI.Port())
if err != nil {
return nil, warnings, fmt.Errorf("parent %v port '%v' was not an integer", orgURI, orgURI.Port())
}
// txt += "dest_domain=" + orgURI.Hostname() + " port=" + orgURI.Port()
parents, secondaryParents, parentWarnings, err := getTopologyParents(server, ds, serversWithParams, parentConfigParams, topology, serverPlacement.IsLastTier, serverCapabilities, dsRequiredCapabilities, dsOrigins, dsParams.MergeGroups)
warnings = append(warnings, parentWarnings...)
if err != nil {
return nil, warnings, errors.New("getting topology parents for '" + *ds.XMLID + "': skipping! " + err.Error())
}
if len(parents) == 0 {
if len(secondaryParents) > 0 {
warnings = append(warnings, "getting topology parents for '"+*ds.XMLID+"': no parents found! using secondary parents")
parents = secondaryParents
secondaryParents = nil
} else {
return nil, warnings, errors.New("getting topology parents for '" + *ds.XMLID + "': no parents found! skipping! (Does your Topology have a CacheGroup with no servers in it?)")
}
}
txt.Parents = parents
// txt += ` parent="` + strings.Join(parents, `;`) + `"`
if len(secondaryParents) > 0 {
txt.SecondaryParents = secondaryParents
// txt += ` secondary_parent="` + strings.Join(secondaryParents, `;`) + `"`
secondaryModeStr, secondaryModeWarnings := getSecondaryModeStr(dsParams.TryAllPrimariesBeforeSecondary, atsMajorVer, tc.DeliveryServiceName(*ds.XMLID))
warnings = append(warnings, secondaryModeWarnings...)
// txt += secondaryModeStr
txt.SecondaryMode = secondaryModeStr // TODO convert
}
txt.RetryPolicy = getTopologyRoundRobin(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm)
// txt += ` round_robin=` + getTopologyRoundRobin(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm)
txt.GoDirect = getTopologyGoDirect(ds, serverPlacement.IsLastTier, serverPlacement.IsLastCacheTier)
// txt += ` go_direct=` + getTopologyGoDirect(ds, serverPlacement.IsLastTier)
// TODO convert
useQueryStringInParentSelection := (*bool)(nil)
if dsParams.QueryStringHandling != "" {
qs := ParentSelectParamQStringHandlingToBool(dsParams.QueryStringHandling)
if qs != nil {
useQueryStringInParentSelection = qs
} else if dsParams.QueryStringHandling != "" {
warnings = append(warnings, fmt.Sprintf("DS '"+*ds.XMLID+"' has malformed query string handling param '"+dsParams.QueryStringHandling+"', using default %v", useQueryStringInParentSelection))
}
}
tqWarns := []string{}
txt.IgnoreQueryStringInParentSelection, tqWarns = getTopologyQueryStringIgnore(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm, useQueryStringInParentSelection)
warnings = append(warnings, tqWarns...)
// txt += ` qstring=` + getTopologyQueryString(ds, serverParams, serverPlacement.IsLastCacheTier, dsParams.Algorithm, dsParams.QueryStringHandling)
// TODO ensure value is always !goDirect, and determine what to do if not
// txt += getTopologyParentIsProxyStr(serverPlacement.IsLastCacheTier)
// TODO convert
prWarns := []string{}
txt.MaxSimpleRetries, txt.MaxMarkdownRetries, txt.MarkdownResponseCodes, txt.ErrorResponseCodes, prWarns = getParentRetryStr(serverPlacement.IsLastCacheTier, atsMajorVer, dsParams.ParentRetry, dsParams.UnavailableServerRetryResponses, dsParams.MaxSimpleRetries, dsParams.MaxUnavailableServerRetries)
warnings = append(warnings, prWarns...)
// txt += getParentRetryStr(serverPlacement.IsLastCacheTier, atsMajorVer, dsParams.ParentRetry, dsParams.UnavailableServerRetryResponses, dsParams.MaxSimpleRetries, dsParams.MaxUnavailableServerRetries)
// txt += "\n"
if dsParams.UsePeering {
txt.SecondaryMode = ParentAbstractionServiceParentSecondaryModePeering
}
return txt, warnings, nil
}
// getParentRetryStr builds the parent retry directive(s).
//
// Returns the MaxSimpleRetries, MaxMarkdownRetries, MarkdownResponseCodes, and ErrorResponseCodes.
//
// If atsMajorVer < 6, "" is returned (ATS 5 and below don't support retry directives).
// If isLastCacheTier is false, "" is returned. This argument exists to simplify usage.
// If parentRetry is "", "" is returned (because the other directives are unused if parent_retry doesn't exist). This is allowed to simplify usage.
// If unavailableServerRetryResponses is not "", it must be valid. Use unavailableServerRetryResponsesValid to check.
// If maxSimpleRetries is "", ParentConfigDSParamDefaultMaxSimpleRetries will be used.
// If maxUnavailableServerRetries is "", ParentConfigDSParamDefaultMaxUnavailableServerRetries will be used.
//
// Does not return errors. If any input is malformed, warnings are returned and that value is set to -1.
//
func getParentRetryStr(isLastCacheTier bool, atsMajorVer int, parentRetry string, unavailableServerRetryResponses string, maxSimpleRetries string, maxUnavailableServerRetries string) (int, int, []int, []int, []string) {
warnings := []string{}
if !isLastCacheTier || // allow !isLastCacheTier, to simplify usage.
parentRetry == "" || // allow parentRetry to be empty, to simplify usage.
atsMajorVer < 6 { // ATS 5 and below don't support parent_retry directives
// warnings = append(warnings, "ATS 5 doesn't support parent retry, not using parent retry values")
return -1, -1, nil, nil, warnings // TODO move to formatter?
}
err := error(nil)
maxSimpleRetriesInt := ParentConfigDSParamDefaultMaxSimpleRetries
if maxSimpleRetries != "" {
maxSimpleRetriesInt, err = strconv.Atoi(maxSimpleRetries)
if err != nil {
maxSimpleRetriesInt = ParentConfigDSParamDefaultMaxSimpleRetries
warnings = append(warnings, "malformed maxSimpleRetries '"+maxSimpleRetries+"', using default "+strconv.Itoa(maxSimpleRetriesInt))
}
}
maxUnavailableServerRetriesInt := ParentConfigDSParamDefaultMaxUnavailableServerRetries
if maxUnavailableServerRetries != "" {
maxUnavailableServerRetriesInt, err = strconv.Atoi(maxUnavailableServerRetries)
if err != nil {
maxUnavailableServerRetriesInt = ParentConfigDSParamDefaultMaxUnavailableServerRetries
warnings = append(warnings, "malformed maxUnavailableServerRetries '"+maxUnavailableServerRetries+"', using default "+strconv.Itoa(maxUnavailableServerRetriesInt))
}
}
unavailableServerRetryResponsesInts, err := ParseRetryResponses(unavailableServerRetryResponses)
if err != nil {
warnings = append(warnings, "malformed unavailableServerRetryResponses '"+unavailableServerRetryResponses+"', using default")
unavailableServerRetryResponsesInts = []int{}
}
simpleRetryResponsesInts := []int{}
// TODO add support for 9.1
// simpleRetryResponsesInts, err := ParseRetryResponses(simpleRetryResponses)
// if err != nil {
// warnings = append(warnings, "malformed simpleRetryResponses '"+simpleRetryResponses+"', using default")
// simpleRetryResponsesInts = []int{}
// }
// TODO make consts
switch strings.ToLower(strings.TrimSpace(parentRetry)) {
case "simple_retry":
unavailableServerRetryResponsesInts = []int{}
if len(simpleRetryResponsesInts) == 0 {
simpleRetryResponsesInts = append(simpleRetryResponsesInts, DefaultSimpleRetryCodes...)
}
case "unavailable_server_retry":
simpleRetryResponsesInts = []int{}
if len(unavailableServerRetryResponsesInts) == 0 {
unavailableServerRetryResponsesInts = append(unavailableServerRetryResponsesInts, DefaultUnavailableServerRetryCodes...)
}
case "both":
if len(unavailableServerRetryResponsesInts) == 0 {
unavailableServerRetryResponsesInts = append(unavailableServerRetryResponsesInts, DefaultUnavailableServerRetryCodes...)
}
if len(simpleRetryResponsesInts) == 0 {
simpleRetryResponsesInts = append(simpleRetryResponsesInts, DefaultSimpleRetryCodes...)
}
default:
unavailableServerRetryResponsesInts = []int{}
simpleRetryResponsesInts = []int{}
}
// txt := ` parent_retry=` + parentRetry
// if unavailableServerRetryResponses != "" {
// txt += ` unavailable_server_retry_responses=` + unavailableServerRetryResponses
// }
// txt += ` max_simple_retries=` + maxSimpleRetries + ` max_unavailable_server_retries=` + maxUnavailableServerRetries
return maxSimpleRetriesInt, maxUnavailableServerRetriesInt, unavailableServerRetryResponsesInts, simpleRetryResponsesInts, warnings
}
// getSecondaryModeStr returns the secondary_mode string, and any warnings.
func getSecondaryModeStr(tryAllPrimariesBeforeSecondary bool, atsMajorVer int, ds tc.DeliveryServiceName) (ParentAbstractionServiceParentSecondaryMode, []string) {
warnings := []string{}
if !tryAllPrimariesBeforeSecondary {
return ParentAbstractionServiceParentSecondaryModeDefault, warnings
}
if atsMajorVer < 8 {
warnings = append(warnings, "DS '"+string(ds)+"' had Parameter "+ParentConfigParamSecondaryMode+" but this cache is "+strconv.Itoa(atsMajorVer)+" and secondary_mode isn't supported in ATS until 8. Not using!")
return ParentAbstractionServiceParentSecondaryModeDefault, warnings
}
// See https://docs.trafficserver.apache.org/en/8.0.x/admin-guide/files/parent.config.en.html
return ParentAbstractionServiceParentSecondaryModeExhaust, warnings
}
func getTopologyParentIsProxyStr(serverIsLastCacheTier bool) string {
if serverIsLastCacheTier {
return ` parent_is_proxy=false`
}
return ""
}
// RetryPolicy
func getTopologyRoundRobin(
ds *DeliveryService,
serverParams map[string]string,
serverIsLastTier bool,
algorithm ParentAbstractionServiceRetryPolicy,
) ParentAbstractionServiceRetryPolicy {
if !serverIsLastTier {
return ParentAbstractionServiceRetryPolicyConsistentHash
}
if parentSelectAlg := serverParams[ParentConfigParamAlgorithm]; ds.OriginShield != nil && *ds.OriginShield != "" && strings.TrimSpace(parentSelectAlg) != "" {
if policy := ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(parentSelectAlg); policy != ParentAbstractionServiceRetryPolicyInvalid {
return policy
}
}
if algorithm != "" {
return algorithm
}
return ParentAbstractionServiceRetryPolicyConsistentHash
}
func getTopologyGoDirect(ds *DeliveryService, serverIsLastTier bool, serverIsLastCacheTier bool) bool {
if serverIsLastCacheTier {
return true
}
if !serverIsLastTier {
return false
}
if ds.OriginShield != nil && *ds.OriginShield != "" {
return true
}
if ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin {
return false
}
return true
}
func getTopologyQueryStringIgnore(
ds *DeliveryService,
serverParams map[string]string,
serverIsLastTier bool,
algorithm ParentAbstractionServiceRetryPolicy,
qStringHandling *bool,
) (bool, []string) {
warnings := []string{}
if serverIsLastTier {
if ds.MultiSiteOrigin != nil && *ds.MultiSiteOrigin && qStringHandling == nil && algorithm == ParentAbstractionServiceRetryPolicyConsistentHash && ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp {
return false, warnings
}
if qStringHandling != nil {
return !(*qStringHandling), warnings
}
return true, warnings
}
if param := serverParams[ParentConfigParamQStringHandling]; param != "" {
if useQStr := ParentSelectParamQStringHandlingToBool(param); useQStr != nil {
return !(*useQStr), warnings
} else if param != "" {
warnings = append(warnings, "Server param '"+ParentConfigParamQStringHandling+"' value '"+param+"' malformed, not using!")
}
// TODO warn if parsing fails?
}
if qStringHandling != nil {
return !(*qStringHandling), warnings
}
if ds.QStringIgnore != nil && tc.QStringIgnore(*ds.QStringIgnore) == tc.QStringIgnoreUseInCacheKeyAndPassUp {
return false, warnings
}
return true, warnings
}
// 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, and any warnings.
func serverParentageParams(sv *Server, allParentConfigParams []parameterWithProfilesMap) (parentServerParams, []string) {
warnings := []string{}
// TODO deduplicate with atstccfg/parentdotconfig.go
parentServerParams := defaultParentServerParams()
if sv.TCPPort != nil {
parentServerParams.Port = *sv.TCPPort
}
serverParentConfigParams := layerProfilesFromMap(sv.ProfileNames, allParentConfigParams)
for _, param := range serverParentConfigParams {
switch param.Name {
case ParentConfigCacheParamWeight:
parentServerParams.Weight = param.Value
case ParentConfigCacheParamPort:
if i, err := strconv.Atoi(param.Value); err != nil {
warnings = append(warnings, "port param is not an integer, skipping! : "+err.Error())
} else {
parentServerParams.Port = i
}
case ParentConfigCacheParamUseIP:
parentServerParams.UseIP = param.Value == "1"
case ParentConfigCacheParamRank:
if i, err := strconv.Atoi(param.Value); err != nil {
warnings = append(warnings, "rank param is not an integer, skipping! : "+err.Error())
} else {
parentServerParams.Rank = i
}
case ParentConfigCacheParamNotAParent:
parentServerParams.NotAParent = param.Value != "false"
}
}
return parentServerParams, warnings
}
func serverParentStr(sv *Server, svParams parentServerParams) (*ParentAbstractionServiceParent, error) {
if svParams.NotAParent {
return nil, nil
}
host := ""
if svParams.UseIP {
// TODO get service interface here
ip := getServerIPAddress(sv)
if ip == nil {
return nil, errors.New("server params Use IP, but has no valid IPv4 Service Address")
}
host = ip.String()
} else {
host = *sv.HostName + "." + *sv.DomainName
}
weight, err := strconv.ParseFloat(svParams.Weight, 64)
if err != nil {
// TODO warn? error?
weight = DefaultParentWeight
}
return &ParentAbstractionServiceParent{
FQDN: host,
Port: svParams.Port,
Weight: weight,
}, nil
}
// getTopologyParents returns the parents, secondary parents, any warnings, and any error.
func getTopologyParents(
server *Server,
ds *DeliveryService,
serversWithParams []serverWithParams,
parentConfigParams []parameterWithProfilesMap, // all params with configFile parent.config
topology tc.Topology,
serverIsLastTier bool,
serverCapabilities map[int]map[ServerCapability]struct{},
dsRequiredCapabilities map[int]map[ServerCapability]struct{},
dsOrigins map[ServerID]struct{}, // for Topology DSes, MSO still needs DeliveryServiceServer assignments.
dsMergeGroups []string, // sorted parent merge groups for this ds
) ([]*ParentAbstractionServiceParent, []*ParentAbstractionServiceParent, []string, error) {
warnings := []string{}
// 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, orgWarns, err := getOriginURI(*ds.OrgServerFQDN) // TODO pass, instead of calling again
warnings = append(warnings, orgWarns...)
if err != nil {
return nil, nil, warnings, err
}
orgPort, err := strconv.Atoi(orgURI.Port())
if err != nil {
warnings = append(warnings, "DS "+*ds.XMLID+" origin '"+*ds.OrgServerFQDN+"' failed to parse port, using 80!")
orgPort = 80
}
parent := &ParentAbstractionServiceParent{
FQDN: orgURI.Hostname(),
Port: orgPort,
Weight: DefaultParentWeight,
}
return []*ParentAbstractionServiceParent{parent}, nil, warnings, nil
}
svNode := tc.TopologyNode{}
for _, node := range topology.Nodes {
if node.Cachegroup == *server.Cachegroup {
svNode = node
break
}
}
if svNode.Cachegroup == "" {
return nil, nil, warnings, errors.New("This server '" + *server.HostName + "' not in DS " + *ds.XMLID + " topology, skipping")
}
if len(svNode.Parents) == 0 {
return nil, nil, warnings, errors.New("DS " + *ds.XMLID + " topology '" + *ds.Topology + "' is last tier, but NonLastTier called! Should never happen")
}
if numParents := len(svNode.Parents); numParents > 2 {
warnings = append(warnings, "DS "+*ds.XMLID+" 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] {
return nil, nil, warnings, errors.New("DS " + *ds.XMLID + " topology '" + *ds.Topology + "' node parent " + strconv.Itoa(svNode.Parents[0]) + " greater than number of topology nodes " + strconv.Itoa(len(topology.Nodes)) + ". Cannot create parents!")
}
if len(svNode.Parents) > 1 && len(topology.Nodes) <= svNode.Parents[1] {
warnings = append(warnings, "DS "+*ds.XMLID+" topology '"+*ds.Topology+"' node secondary parent "+strconv.Itoa(svNode.Parents[1])+" greater than number of topology nodes "+strconv.Itoa(len(topology.Nodes))+". Secondary parent will be ignored!")
}
parentCG := topology.Nodes[svNode.Parents[0]].Cachegroup
secondaryParentCG := ""
if len(svNode.Parents) > 1 && len(topology.Nodes) > svNode.Parents[1] {
secondaryParentCG = topology.Nodes[svNode.Parents[1]].Cachegroup
}
if parentCG == "" {
return nil, nil, warnings, errors.New("Server '" + *server.HostName + "' DS " + *ds.XMLID + " topology '" + *ds.Topology + "' cachegroup '" + *server.Cachegroup + "' topology node parent " + strconv.Itoa(svNode.Parents[0]) + " is not in the topology!")
}
parentStrs := []*ParentAbstractionServiceParent{}
secondaryParentStrs := []*ParentAbstractionServiceParent{}
for _, sv := range serversWithParams {
if sv.ID == nil {
warnings = append(warnings, "TO Servers server had nil ID, skipping")
continue
} else if sv.Cachegroup == nil {
warnings = append(warnings, "TO Servers server had nil Cachegroup, skipping")
continue
} else if sv.CDNName == nil {
warnings = append(warnings, "TO servers had server with missing CDNName, skipping!")
continue
} else if sv.Status == nil || *sv.Status == "" {
warnings = append(warnings, "TO servers had server with missing Status, skipping!")
continue
}
if !strings.HasPrefix(sv.Type, tc.EdgeTypePrefix) && !strings.HasPrefix(sv.Type, tc.MidTypePrefix) && sv.Type != tc.OriginTypeName {
continue // only consider edges, mids, and origins in the CacheGroup.
}
if _, dsHasOrigin := dsOrigins[ServerID(*sv.ID)]; sv.Type == tc.OriginTypeName && !dsHasOrigin {
continue
}
if *sv.CDNName != *server.CDNName {
continue
}
if *sv.Status != string(tc.CacheStatusReported) && *sv.Status != string(tc.CacheStatusOnline) {
continue
}
if sv.Type != tc.OriginTypeName && !hasRequiredCapabilities(serverCapabilities[*sv.ID], dsRequiredCapabilities[*ds.ID]) {
continue
}
if *sv.Cachegroup == parentCG {
parentStr, err := serverParentStr(&sv.Server, sv.Params)
if err != nil {
return nil, nil, warnings, errors.New("getting server parent string: " + err.Error())
}
if parentStr != nil { // will be nil if server is not_a_parent (possibly other reasons)
parentStrs = append(parentStrs, parentStr)
}
}
if *sv.Cachegroup == secondaryParentCG {
parentStr, err := serverParentStr(&sv.Server, sv.Params)
if err != nil {
return nil, nil, warnings, errors.New("getting server parent string: " + err.Error())
}
secondaryParentStrs = append(secondaryParentStrs, parentStr)
}
}
if 0 < len(dsMergeGroups) && 0 < len(secondaryParentStrs) {
if util.ContainsStr(dsMergeGroups, parentCG) && util.ContainsStr(dsMergeGroups, secondaryParentCG) {
parentStrs = append(parentStrs, secondaryParentStrs...)
secondaryParentStrs = nil
}
}
return parentStrs, secondaryParentStrs, warnings, nil
}
// getOriginURI returns the URL, any warnings, and any error.
func getOriginURI(fqdn string) (*url.URL, []string, error) {
warnings := []string{}
orgURI, err := url.Parse(fqdn) // TODO verify origin is always a host:port
if err != nil {
return nil, warnings, errors.New("parsing: " + err.Error())
}
if orgURI.Port() == "" {
if orgURI.Scheme == "http" {
orgURI.Host += ":80"
} else if orgURI.Scheme == "https" {
orgURI.Host += ":443"
} else {
warnings = append(warnings, "non-top-level: origin '"+fqdn+"' is unknown scheme '"+orgURI.Scheme+"', but has no port! Using as-is! ")
}
}
return orgURI, warnings, nil
}
// getParentStrs returns the primary parents, secondary parents, the secondary mode, and any warnings.
func getParentStrs(
ds *DeliveryService,
dsRequiredCapabilities map[int]map[ServerCapability]struct{},
parentInfos []parentInfo,
atsMajorVer int,
tryAllPrimariesBeforeSecondary bool,
) ([]*ParentAbstractionServiceParent, []*ParentAbstractionServiceParent, ParentAbstractionServiceParentSecondaryMode, []string) {
warnings := []string{}
parentInfo := []*ParentAbstractionServiceParent{}
secondaryParentInfo := []*ParentAbstractionServiceParent{}
sort.Sort(parentInfoSortByRank(parentInfos))
for _, parent := range parentInfos { // TODO fix magic key
if !hasRequiredCapabilities(parent.Capabilities, dsRequiredCapabilities[*ds.ID]) {
continue
}
pTxt := parent.ToAbstract()
if parent.PrimaryParent {
parentInfo = append(parentInfo, pTxt)
} else if parent.SecondaryParent {
secondaryParentInfo = append(secondaryParentInfo, pTxt)
}
}
if len(parentInfo) == 0 {
parentInfo = secondaryParentInfo
secondaryParentInfo = []*ParentAbstractionServiceParent{}
}
// TODO remove duplicate code with top level if block
seen := map[string]struct{}{} // TODO change to host+port? host isn't unique
parentInfo, seen = RemoveParentDuplicates(parentInfo, seen)
secondaryParentInfo, seen = RemoveParentDuplicates(secondaryParentInfo, seen)
dsName := tc.DeliveryServiceName("")
if ds != nil && ds.XMLID != nil {
dsName = tc.DeliveryServiceName(*ds.XMLID)
}
// parents := ""
// secondaryParents := "" // "secparents" in Perl
// TODO the abstract->text needs to take this into account
// if atsMajorVer >= 6 && len(secondaryParentInfo) > 0 {
// parents = `parent="` + strings.Join(parentInfo, "") + `"`
// secondaryParents = ` secondary_parent="` + strings.Join(secondaryParentInfo, "") + `"`
secondaryMode, secondaryModeWarnings := getSecondaryModeStr(tryAllPrimariesBeforeSecondary, atsMajorVer, dsName)
warnings = append(warnings, secondaryModeWarnings...)
// secondaryParents += secondaryModeStr
// } else {
// parents = `parent="` + strings.Join(parentInfo, "") + strings.Join(secondaryParentInfo, "") + `"`
// }
return parentInfo, secondaryParentInfo, secondaryMode, warnings
}
// getMSOParentStrs returns the parents= and secondary_parents= strings for ATS parent.config lines for MSO, and any warnings.
func getMSOParentStrs(
ds *DeliveryService,
parentInfos []parentInfo,
atsMajorVer int,
msoAlgorithm ParentAbstractionServiceRetryPolicy,
tryAllPrimariesBeforeSecondary bool,
) ([]*ParentAbstractionServiceParent, []*ParentAbstractionServiceParent, ParentAbstractionServiceParentSecondaryMode, []string) {
warnings := []string{}
// TODO determine why MSO is different, and if possible, combine with getParentAndSecondaryParentStrs.
rankedParents := parentInfoSortByRank(parentInfos)
sort.Sort(rankedParents)
parentInfoTxt := []*ParentAbstractionServiceParent{}
secondaryParentInfo := []*ParentAbstractionServiceParent{}
nullParentInfo := []*ParentAbstractionServiceParent{}
for _, parent := range ([]parentInfo)(rankedParents) {
if parent.PrimaryParent {
parentInfoTxt = append(parentInfoTxt, parent.ToAbstract())
} else if parent.SecondaryParent {
secondaryParentInfo = append(secondaryParentInfo, parent.ToAbstract())
} else {
nullParentInfo = append(nullParentInfo, parent.ToAbstract())
}
}
if len(parentInfoTxt) == 0 {
// If no parents are found in the secondary parent either, then set the null parent list (parents in neither secondary or primary)
// as the secondary parent list and clear the null parent list.
if len(secondaryParentInfo) == 0 {
secondaryParentInfo = nullParentInfo
nullParentInfo = []*ParentAbstractionServiceParent{}
}
parentInfoTxt = secondaryParentInfo
secondaryParentInfo = []*ParentAbstractionServiceParent{} // TODO should this be '= secondary'? Currently emulates Perl
}
// TODO benchmark, verify this isn't slow. if it is, it could easily be made faster
seen := map[string]struct{}{} // TODO change to host+port? host isn't unique
parentInfoTxt, seen = RemoveParentDuplicates(parentInfoTxt, seen)
secondaryParentInfo, seen = RemoveParentDuplicates(secondaryParentInfo, seen)
nullParentInfo, seen = RemoveParentDuplicates(nullParentInfo, seen)
// secondaryParentStr := strings.Join(secondaryParentInfo, "") + strings.Join(nullParentInfo, "")
secondaryParentInfo = append(secondaryParentInfo, nullParentInfo...)
dsName := tc.DeliveryServiceName("")
if ds != nil && ds.XMLID != nil {
dsName = tc.DeliveryServiceName(*ds.XMLID)
}
// If the ats version supports it and the algorithm is consistent hash, put secondary and non-primary parents into secondary parent group.
// This will ensure that secondary and tertiary parents will be unused unless all hosts in the primary group are unavailable.
// parents := ""
// secondaryParents := ""
// TODO add this logic to the abstraction->text converter
// if atsMajorVer >= 6 && msoAlgorithm == "consistent_hash" && len(secondaryParentStr) > 0 {
// parents = `parent="` + strings.Join(parentInfoTxt, "") + `"`
// secondaryParents = ` secondary_parent="` + secondaryParentStr + `"`
secondaryMode, secondaryModeWarnings := getSecondaryModeStr(tryAllPrimariesBeforeSecondary, atsMajorVer, dsName)
warnings = append(warnings, secondaryModeWarnings...)
// secondaryParents += secondaryModeStr
// } else {
// parents = `parent="` + strings.Join(parentInfoTxt, "") + secondaryParentStr + `"`
// }
return parentInfoTxt, secondaryParentInfo, secondaryMode, warnings
}
// makeParentInfo returns the parent info and any warnings
func makeParentInfo(
serverParentCGData serverParentCacheGroupData,
serverDomain string, // getCDNDomainByProfileID(tx, server.ProfileID)
originServers map[OriginHost][]serverWithParams,
serverCapabilities map[int]map[ServerCapability]struct{},
) (map[OriginHost][]parentInfo, []string) {
warnings := []string{}
parentInfos := map[OriginHost][]parentInfo{}
// note servers also contains an "all" key
for originHost, servers := range originServers {
for _, sv := range servers {
if sv.Params.NotAParent {
continue
}
// Perl has this check, but we only select servers ("deliveryServices" in Perl) with the right CDN in the first place
// if profile.Domain != serverDomain {
// continue
// }
weight, err := strconv.ParseFloat(sv.Params.Weight, 64)
if err != nil {
warnings = append(warnings, "server "+*sv.HostName+" profile had malformed weight, using default!")
weight = DefaultParentWeight
}
ipAddr := getServerIPAddress(&sv.Server)
if ipAddr == nil {
warnings = append(warnings, "making parent info: got server with no valid IP Address, skipping!")
continue
}
parentInf := parentInfo{
Host: *sv.HostName,
Port: sv.Params.Port,
Domain: *sv.DomainName,
Weight: weight,
UseIP: sv.Params.UseIP,
Rank: sv.Params.Rank,
IP: ipAddr.String(),
PrimaryParent: serverParentCGData.ParentID == *sv.CachegroupID,
SecondaryParent: serverParentCGData.SecondaryParentID == *sv.CachegroupID,
Capabilities: serverCapabilities[*sv.ID],
}
if parentInf.Port < 1 {
parentInf.Port = *sv.TCPPort
}
parentInfos[originHost] = append(parentInfos[originHost], parentInf)
}
}
return parentInfos, warnings
}
// unavailableServerRetryResponsesValid returns whether a unavailable_server_retry_responses parameter is valid for an ATS parent rule.
func unavailableServerRetryResponsesValid(s string) bool {
// optimization if param is empty
if s == "" {
return false
}
re := regexp.MustCompile(`^"(:?\d{3},)+\d{3}"\s*$`) // TODO benchmark, cache if performance matters
return re.MatchString(s)
}
// getOriginServers returns the origin servers with parameters, any warnings, and any error.
func getOriginServers(
cgServers map[int]serverWithParams,
parentServerDSes map[int]map[int]struct{},
dses []DeliveryService,
serverCapabilities map[int]map[ServerCapability]struct{},
) (map[OriginHost][]serverWithParams, []string, error) {
warnings := []string{}
originServers := map[OriginHost][]serverWithParams{}
dsIDMap := map[int]DeliveryService{}
for _, ds := range dses {
if ds.ID == nil {
return nil, warnings, errors.New("delivery services got nil ID!")
}
if !ds.Type.IsHTTP() && !ds.Type.IsDNS() {
continue // skip ANY_MAP, STEERING, etc
}
dsIDMap[*ds.ID] = ds
}
allDSMap := map[int]DeliveryService{} // all DSes for this server, NOT all dses in TO
for _, dsIDs := range parentServerDSes {
for dsID, _ := range dsIDs {
if _, ok := dsIDMap[dsID]; !ok {
// this is normal if the TO was too old to understand our /deliveryserviceserver?servers= query param
// In which case, the DSS will include DSes from other CDNs, which aren't in the dsIDMap
// If the server was new enough to respect the params, this should never happen.
// warnings = append(warnings, ("getting delivery services: parent server DS %v not in dsIDMap\n", dsID)
continue
}
if _, ok := allDSMap[dsID]; !ok {
allDSMap[dsID] = dsIDMap[dsID]
}
}
}
dsOrigins, dsOriginWarns, err := getDSOrigins(allDSMap)
warnings = append(warnings, dsOriginWarns...)
if err != nil {
return nil, warnings, errors.New("getting DS origins: " + err.Error())
}
for _, cgSv := range cgServers {
if cgSv.ID == nil {
warnings = append(warnings, "getting origin servers: got server with nil ID, skipping!")
continue
} else if cgSv.HostName == nil {
warnings = append(warnings, "getting origin servers: got server with nil HostName, skipping!")
continue
} else if cgSv.TCPPort == nil {
warnings = append(warnings, "getting origin servers: got server with nil TCPPort, skipping!")
continue
} else if cgSv.CachegroupID == nil {
warnings = append(warnings, "getting origin servers: got server with nil CachegroupID, skipping!")
continue
} else if cgSv.StatusID == nil {
warnings = append(warnings, "getting origin servers: got server with nil StatusID, skipping!")
continue
} else if cgSv.TypeID == nil {
warnings = append(warnings, "getting origin servers: got server with nil TypeID, skipping!")
continue
} else if len(cgSv.ProfileNames) == 0 {
warnings = append(warnings, "getting origin servers: got server with no profile names, skipping!")
continue
} else if cgSv.CDNID == nil {
warnings = append(warnings, "getting origin servers: got server with nil CDNID, skipping!")
continue
} else if cgSv.DomainName == nil {
warnings = append(warnings, "getting origin servers: got server with nil DomainName, skipping!")
continue
}
ipAddr := getServerIPAddress(&cgSv.Server)
if ipAddr == nil {
warnings = append(warnings, "getting origin servers: got server with no valid IP Address, skipping!")
continue
}
if cgSv.Type == tc.OriginTypeName {
for dsID, _ := range parentServerDSes[*cgSv.ID] { // map[serverID][]dsID
orgURI := dsOrigins[dsID]
if orgURI == nil {
// warnings = append(warnings, fmt.Sprintf(("ds %v has no origins! Skipping!\n", dsID) // TODO determine if this is normal
continue
}
orgHost := OriginHost(orgURI.Host)
originServers[orgHost] = append(originServers[orgHost], cgSv)
}
} else {
originServers[deliveryServicesAllParentsKey] = append(originServers[deliveryServicesAllParentsKey], cgSv)
}
}
return originServers, warnings, nil
}
// getDSOrigins takes a map[deliveryServiceID]DeliveryService, and returns a map[DeliveryServiceID]OriginURI, any warnings, and any error.
func getDSOrigins(dses map[int]DeliveryService) (map[int]*originURI, []string, error) {
warnings := []string{}
dsOrigins := map[int]*originURI{}
for _, ds := range dses {
if ds.ID == nil {
return nil, warnings, errors.New("ds has nil ID")
}
if ds.XMLID == nil {
return nil, warnings, errors.New("ds has nil XMLID")
}
if ds.OrgServerFQDN == nil {
warnings = append(warnings, fmt.Sprintf("GetDSOrigins ds %v got nil OrgServerFQDN, skipping!\n", *ds.XMLID))
continue
}
orgURL, err := url.Parse(*ds.OrgServerFQDN)
if err != nil {
return nil, warnings, errors.New("parsing ds '" + *ds.XMLID + "' OrgServerFQDN '" + *ds.OrgServerFQDN + "': " + err.Error())
}
if orgURL.Scheme == "" {
return nil, warnings, errors.New("parsing ds '" + *ds.XMLID + "' OrgServerFQDN '" + *ds.OrgServerFQDN + "': " + "missing scheme")
}
if orgURL.Host == "" {
return nil, warnings, errors.New("parsing ds '" + *ds.XMLID + "' OrgServerFQDN '" + *ds.OrgServerFQDN + "': " + "missing scheme")
}
scheme := orgURL.Scheme
host := orgURL.Hostname()
port := orgURL.Port()
if port == "" {
if scheme == "http" {
port = "80"
} else if scheme == "https" {
port = "443"
} else {
warnings = append(warnings, "parsing ds '"+*ds.XMLID+"' OrgServerFQDN '"+*ds.OrgServerFQDN+"': "+"unknown scheme '"+scheme+"' and no port, leaving port empty!")
}
}
dsOrigins[*ds.ID] = &originURI{Scheme: scheme, Host: host, Port: port}
}
return dsOrigins, warnings, nil
}
// makeDSOrigins returns the DS Origins and any warnings.
func makeDSOrigins(dsses []DeliveryServiceServer, dses []DeliveryService, servers []Server) (map[DeliveryServiceID]map[ServerID]struct{}, []string) {
warnings := []string{}
dssMap := map[DeliveryServiceID]map[ServerID]struct{}{}
for _, dss := range dsses {
dsID := DeliveryServiceID(dss.DeliveryService)
serverID := ServerID(dss.Server)
if dssMap[dsID] == nil {
dssMap[dsID] = map[ServerID]struct{}{}
}
dssMap[dsID][serverID] = struct{}{}
}
svMap := map[ServerID]Server{}
for _, sv := range servers {
if sv.ID == nil {
warnings = append(warnings, "got server with missing ID, skipping!")
}
svMap[ServerID(*sv.ID)] = sv
}
dsOrigins := map[DeliveryServiceID]map[ServerID]struct{}{}
for _, ds := range dses {
if ds.ID == nil {
warnings = append(warnings, "got ds with missing ID, skipping!")
continue
}
dsID := DeliveryServiceID(*ds.ID)
assignedServers := dssMap[dsID]
for svID, _ := range assignedServers {
sv := svMap[svID]
if sv.Type != tc.OriginTypeName {
continue
}
if dsOrigins[dsID] == nil {
dsOrigins[dsID] = map[ServerID]struct{}{}
}
dsOrigins[dsID][svID] = struct{}{}
}
}
return dsOrigins, warnings
}
// getProfileParentConfigParams returns a map[profileName][paramName]paramVal and any warnings
func getProfileParentConfigParams(tcParentConfigParams []tc.Parameter) (map[string]map[string]string, []string) {
warnings := []string{}
parentConfigParamsWithProfiles, err := tcParamsToParamsWithProfiles(tcParentConfigParams)
if err != nil {
warnings = append(warnings, "error getting profiles from Traffic Ops Parameters, Parameters will not be considered for generation! : "+err.Error())
parentConfigParamsWithProfiles = []parameterWithProfiles{}
}
// this is an optimization, to avoid looping over all params, for every DS. Instead, we loop over all params only once, and put them in a profile map.
profileParentConfigParams := map[string]map[string]string{} // map[profileName][paramName]paramVal
for _, param := range parentConfigParamsWithProfiles {
for _, profile := range param.ProfileNames {
if _, ok := profileParentConfigParams[profile]; !ok {
profileParentConfigParams[profile] = map[string]string{}
}
profileParentConfigParams[profile][param.Name] = param.Value
}
}
return profileParentConfigParams, warnings
}
// getServerParentConfigParams returns a map[name]value.
// Intended to be called with the result of getProfileParentConfigParams.
func getServerParentConfigParams(server *Server, allParentConfigParams []parameterWithProfilesMap) map[string]string {
// We only need parent.config params, don't need all the params on the server
serverParams := map[string]string{}
serverParentConfigParams := layerProfilesFromMap(server.ProfileNames, allParentConfigParams)
for _, pa := range serverParentConfigParams {
name := pa.Name
val := pa.Value
if name == ParentConfigParamQStringHandling ||
name == ParentConfigParamAlgorithm ||
name == ParentConfigParamQString {
serverParams[name] = val
}
}
return serverParams
}