| package atscfg |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import ( |
| "errors" |
| "github.com/apache/trafficcontrol/lib/go-log" |
| "path/filepath" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "github.com/apache/trafficcontrol/lib/go-tc" |
| "github.com/apache/trafficcontrol/lib/go-util" |
| ) |
| |
| const CacheKeyParameterConfigFile = "cachekey.config" |
| const ContentTypeRemapDotConfig = ContentTypeTextASCII |
| const LineCommentRemapDotConfig = LineCommentHash |
| |
| const RemapConfigRangeDirective = `__RANGE_DIRECTIVE__` |
| |
| // RemapDotConfigOpts contains settings to configure generation options. |
| type RemapDotConfigOpts struct { |
| // VerboseComments 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. |
| VerboseComments 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 |
| |
| // UseStrategies is whether to use strategies.yaml rather than parent.config. |
| UseStrategies bool |
| // UseCoreStrategies is whether to use the ATS core strategies, rather than the parent_select plugin. |
| // This has no effect if UseStrategies is false. |
| UseStrategiesCore bool |
| } |
| |
| func MakeRemapDotConfig( |
| server *Server, |
| unfilteredDSes []DeliveryService, |
| dss []DeliveryServiceServer, |
| dsRegexArr []tc.DeliveryServiceRegexes, |
| serverParams []tc.Parameter, |
| cdn *tc.CDN, |
| remapConfigParams []tc.Parameter, // includes cachekey.config |
| topologies []tc.Topology, |
| cacheGroupArr []tc.CacheGroupNullable, |
| serverCapabilities map[int]map[ServerCapability]struct{}, |
| dsRequiredCapabilities map[int]map[ServerCapability]struct{}, |
| configDir string, |
| opt *RemapDotConfigOpts, |
| ) (Cfg, error) { |
| if opt == nil { |
| opt = &RemapDotConfigOpts{} |
| } |
| warnings := []string{} |
| |
| if !opt.UseStrategies && opt.UseStrategiesCore { |
| warnings = append(warnings, "opt.UseStrategies was false, but opt.UseStrategiesCore was set, which has no effect! Not using strategies, per opt.UseStrategies.") |
| } |
| |
| if server.HostName == nil { |
| return Cfg{}, makeErr(warnings, "server HostName missing") |
| } else if server.ID == nil { |
| return Cfg{}, makeErr(warnings, "server ID missing") |
| } else if server.Cachegroup == nil { |
| return Cfg{}, makeErr(warnings, "server Cachegroup missing") |
| } else if server.DomainName == nil { |
| return Cfg{}, makeErr(warnings, "server DomainName missing") |
| } |
| |
| cdnDomain := cdn.DomainName |
| dsRegexes := makeDSRegexMap(dsRegexArr) |
| // Returned DSes are guaranteed to have a non-nil XMLID, Type, DSCP, ID, and Active. |
| dses, dsWarns := remapFilterDSes(server, dss, unfilteredDSes) |
| warnings = append(warnings, dsWarns...) |
| |
| dsProfilesConfigParams, paramWarns, err := makeDSProfilesConfigParams(server, dses, remapConfigParams) |
| warnings = append(warnings, paramWarns...) |
| if err != nil { |
| warnings = append(warnings, "making Delivery Service Cache Key Params, cache key will be missing! : "+err.Error()) |
| } |
| |
| atsMajorVersion, verWarns := getATSMajorVersion(serverParams) |
| warnings = append(warnings, verWarns...) |
| serverPackageParamData, paramWarns := makeServerPackageParamData(server, serverParams) |
| warnings = append(warnings, paramWarns...) |
| cacheGroups, err := makeCGMap(cacheGroupArr) |
| if err != nil { |
| return Cfg{}, makeErr(warnings, "making remap.config, config will be malformed! : "+err.Error()) |
| } |
| |
| nameTopologies := makeTopologyNameMap(topologies) |
| |
| hdr := makeHdrComment(opt.HdrComment) |
| txt := "" |
| typeWarns := []string{} |
| if tc.CacheTypeFromString(server.Type) == tc.CacheTypeMid { |
| txt, typeWarns, err = getServerConfigRemapDotConfigForMid(atsMajorVersion, dsProfilesConfigParams, dses, dsRegexes, hdr, server, nameTopologies, cacheGroups, serverCapabilities, dsRequiredCapabilities, configDir, opt) |
| } else { |
| txt, typeWarns, err = getServerConfigRemapDotConfigForEdge(dsProfilesConfigParams, serverPackageParamData, dses, dsRegexes, atsMajorVersion, hdr, server, nameTopologies, cacheGroups, serverCapabilities, dsRequiredCapabilities, cdnDomain, configDir, opt) |
| } |
| warnings = append(warnings, typeWarns...) |
| if err != nil { |
| return Cfg{}, makeErr(warnings, err.Error()) // the GetFor funcs include error context |
| } |
| |
| return Cfg{ |
| Text: txt, |
| ContentType: ContentTypeRemapDotConfig, |
| LineComment: LineCommentRemapDotConfig, |
| Warnings: warnings, |
| }, nil |
| } |
| |
| // This sticks the DS parameters in a map. |
| // remap.config parameters use "<plugin>.pparam" key |
| // cachekey.config parameters retain the 'cachekey.config' key |
| func classifyConfigParams(configParams []tc.Parameter) map[string][]tc.Parameter { |
| configParamMap := map[string][]tc.Parameter{} |
| for _, param := range configParams { |
| key := param.ConfigFile |
| if "remap.config" == key { |
| key = param.Name |
| } |
| configParamMap[key] = append(configParamMap[key], param) |
| } |
| return configParamMap |
| } |
| |
| // For general <plugin>.pparam parameters |
| func paramsStringFor(parameters []tc.Parameter, warnings *[]string) (paramsString string) { |
| uniquemap := map[string]int{} |
| |
| for _, param := range parameters { |
| paramsString += " @pparam=" + param.Value |
| |
| // Try to extract argument |
| index := strings.IndexAny(param.Value, "= ") |
| arg := "" |
| if 0 < index { |
| arg = param.Value[:index] |
| } else { |
| arg = param.Value |
| } |
| |
| // Warn on detection, but don't correct |
| if _, exists := uniquemap[arg]; !exists { |
| uniquemap[arg] = 1 |
| } else { |
| *warnings = append(*warnings, "Multiple repeated arguments '"+arg+"'") |
| } |
| } |
| return |
| } |
| |
| // for parameters that use 'cachekey.config' as their key |
| func paramsStringOldFor(parameters []tc.Parameter, warnings *[]string) (paramsString string) { |
| // check for duplicate parameters |
| uniquemap := map[string]int{} |
| paramKeyVals := []keyVal{} |
| for _, param := range parameters { |
| key := param.Name |
| val := param.Value |
| |
| if _, exists := uniquemap[key]; !exists { |
| uniquemap[key] = 1 |
| paramKeyVals = append(paramKeyVals, keyVal{Key: key, Val: val}) |
| } else { |
| uniquemap[key]++ |
| *warnings = append(*warnings, "got multiple parameters for name '"+key+"' - ignoring '"+val+"'") |
| } |
| } |
| |
| sort.Sort(keyVals(paramKeyVals)) |
| for _, keyVal := range paramKeyVals { |
| paramsString += " @pparam=--" + keyVal.Key + "=" + keyVal.Val |
| } |
| return |
| } |
| |
| // Handles special case for cachekey |
| func cachekeyArgsFor(configParamsMap map[string][]tc.Parameter, warnings *[]string) (argsString string) { |
| |
| hasCachekey := false |
| |
| if params, ok := configParamsMap["cachekey.pparam"]; ok { |
| argsString += paramsStringFor(params, warnings) |
| hasCachekey = true |
| } |
| |
| // Add on the cachekey.config |
| if params, ok := configParamsMap["cachekey.config"]; ok { |
| if hasCachekey { |
| *warnings = append(*warnings, "Both new cachekey.pparam and old cachekey.config parameters assigned") |
| } |
| argsString += paramsStringOldFor(params, warnings) |
| } |
| return |
| } |
| |
| // lastPrePostRemapLinesFor Returns any pre or post raw remap lines. |
| func lastPrePostRemapLinesFor(dsConfigParamsMap map[string][]tc.Parameter, dsid string) ([]string, []string) { |
| preRemapLines := []string{} |
| postRemapLines := []string{} |
| |
| // Any raw pre pend |
| if params, ok := dsConfigParamsMap["LastRawRemapPre"]; ok { |
| for _, param := range params { |
| preRemapLines = append(preRemapLines, param.Value+" # Raw: "+dsid+"\n") |
| } |
| } |
| |
| // Any raw post pend |
| if params, ok := dsConfigParamsMap["LastRawRemapPost"]; ok { |
| for _, param := range params { |
| postRemapLines = append(postRemapLines, param.Value+" # Raw: "+dsid+"\n") |
| } |
| } |
| |
| return preRemapLines, postRemapLines |
| } |
| |
| // getServerConfigRemapDotConfigForMid returns the remap lines, any warnings, and any error. |
| func getServerConfigRemapDotConfigForMid( |
| atsMajorVersion int, |
| profilesConfigParams map[int][]tc.Parameter, |
| dses []DeliveryService, |
| dsRegexes map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex, |
| header string, |
| server *Server, |
| nameTopologies map[TopologyName]tc.Topology, |
| cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable, |
| serverCapabilities map[int]map[ServerCapability]struct{}, |
| dsRequiredCapabilities map[int]map[ServerCapability]struct{}, |
| configDir string, |
| opts *RemapDotConfigOpts, |
| ) (string, []string, error) { |
| warnings := []string{} |
| midRemaps := map[string]string{} |
| preRemapLines := []string{} |
| postRemapLines := []string{} |
| for _, ds := range dses { |
| if !hasRequiredCapabilities(serverCapabilities[*server.ID], dsRequiredCapabilities[*ds.ID]) { |
| continue |
| } |
| |
| topology, hasTopology := nameTopologies[TopologyName(*ds.Topology)] |
| if *ds.Topology != "" && hasTopology { |
| topoIncludesServer, err := topologyIncludesServerNullable(topology, server) |
| if err != nil { |
| return "", warnings, errors.New("getting Topology Server inclusion: " + err.Error()) |
| } |
| if !topoIncludesServer { |
| continue |
| } |
| } |
| |
| if !ds.Type.UsesMidCache() && (!hasTopology || *ds.Topology == "") { |
| continue // Live local delivery services skip mids (except Topologies ignore DS types) |
| } |
| |
| if ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" { |
| warnings = append(warnings, "ds '"+*ds.XMLID+"' has no origin fqdn, skipping!") // TODO confirm - Perl uses without checking! |
| continue |
| } |
| |
| remapFrom := strings.Replace(*ds.OrgServerFQDN, `https://`, `http://`, -1) |
| |
| if midRemaps[remapFrom] != "" { |
| continue // skip remap rules from extra HOST_REGEXP entries |
| } |
| |
| midRemap := "" |
| |
| midRemap += strategyDirective(getStrategyName(*ds.XMLID), configDir, opts) |
| |
| if *ds.Topology != "" { |
| topoTxt, err := makeDSTopologyHeaderRewriteTxt(ds, tc.CacheGroupName(*server.Cachegroup), topology, cacheGroups) |
| if err != nil { |
| return "", warnings, err |
| } |
| midRemap += topoTxt |
| } else if (ds.MidHeaderRewrite != nil && *ds.MidHeaderRewrite != "") || (ds.MaxOriginConnections != nil && *ds.MaxOriginConnections > 0) || (ds.ServiceCategory != nil && *ds.ServiceCategory != "") { |
| midRemap += ` @plugin=header_rewrite.so @pparam=` + midHeaderRewriteConfigFileName(*ds.XMLID) |
| } |
| |
| // Logic for handling cachekey params |
| cachekeyArgs := "" |
| |
| // qstring ignore |
| if ds.QStringIgnore != nil && *ds.QStringIgnore == tc.QueryStringIgnoreIgnoreInCacheKeyAndPassUp { |
| cachekeyArgs = getQStringIgnoreRemap(atsMajorVersion) |
| } |
| |
| dsConfigParamsMap := map[string][]tc.Parameter{} |
| if nil != ds.ProfileID { |
| dsConfigParamsMap = classifyConfigParams(profilesConfigParams[*ds.ProfileID]) |
| } |
| |
| if len(dsConfigParamsMap) > 0 { |
| cachekeyArgs += cachekeyArgsFor(dsConfigParamsMap, &warnings) |
| } |
| |
| if cachekeyArgs != "" { |
| midRemap += " @plugin=cachekey.so" + cachekeyArgs |
| } |
| |
| if ds.RangeRequestHandling != nil && (*ds.RangeRequestHandling == tc.RangeRequestHandlingCacheRangeRequest || *ds.RangeRequestHandling == tc.RangeRequestHandlingSlice) { |
| midRemap += " @plugin=cache_range_requests.so" + |
| paramsStringFor(dsConfigParamsMap["cache_range_requests.pparam"], &warnings) |
| } |
| |
| isLastCache, err := serverIsLastCacheForDS(server, &ds, nameTopologies, cacheGroups) |
| if err != nil { |
| return "", warnings, errors.New("determining if cache is the last tier: " + err.Error()) |
| } |
| |
| mapTo := *ds.OrgServerFQDN |
| |
| // if this remap is going to a parent, use http not https. |
| // cache-to-cache communication inside the CDN is always http (though that's likely to change in the future) |
| if !isLastCache { |
| mapTo = strings.Replace(mapTo, `https://`, `http://`, -1) |
| } |
| |
| if midRemap != "" { |
| midRemaps[remapFrom] = mapTo + midRemap |
| } |
| |
| // Any raw pre or post pend |
| dsPreRemaps, dsPostRemaps := lastPrePostRemapLinesFor(dsConfigParamsMap, *ds.XMLID) |
| |
| // Add to pre/post remap lines if this is last tier |
| if len(dsPreRemaps) > 0 || len(dsPostRemaps) > 0 { |
| if isLastCache { |
| preRemapLines = append(preRemapLines, dsPreRemaps...) |
| postRemapLines = append(postRemapLines, dsPostRemaps...) |
| } |
| } |
| } |
| |
| textLines := []string{} |
| |
| for originFQDN, midRemap := range midRemaps { |
| textLines = append(textLines, "map "+originFQDN+" "+midRemap+"\n") |
| } |
| |
| sort.Strings(preRemapLines) |
| sort.Strings(textLines) |
| sort.Strings(postRemapLines) |
| |
| // Prepend any pre remap lines |
| remapLinesAll := append(preRemapLines, textLines...) |
| |
| // Append on any post raw remap lines |
| remapLinesAll = append(remapLinesAll, postRemapLines...) |
| |
| text := header + strings.Join(remapLinesAll, "") |
| return text, warnings, nil |
| } |
| |
| // getServerConfigRemapDotConfigForEdge returns the remap lines, any warnings, and any error. |
| func getServerConfigRemapDotConfigForEdge( |
| profilesRemapConfigParams map[int][]tc.Parameter, |
| serverPackageParamData map[string]string, // map[paramName]paramVal for this server, config file 'package' |
| dses []DeliveryService, |
| dsRegexes map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex, |
| atsMajorVersion int, |
| header string, |
| server *Server, |
| nameTopologies map[TopologyName]tc.Topology, |
| cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable, |
| serverCapabilities map[int]map[ServerCapability]struct{}, |
| dsRequiredCapabilities map[int]map[ServerCapability]struct{}, |
| cdnDomain string, |
| configDir string, |
| opts *RemapDotConfigOpts, |
| ) (string, []string, error) { |
| warnings := []string{} |
| textLines := []string{} |
| preRemapLines := []string{} |
| postRemapLines := []string{} |
| |
| for _, ds := range dses { |
| if !hasRequiredCapabilities(serverCapabilities[*server.ID], dsRequiredCapabilities[*ds.ID]) { |
| continue |
| } |
| |
| topology, hasTopology := nameTopologies[TopologyName(*ds.Topology)] |
| if *ds.Topology != "" && hasTopology { |
| topoIncludesServer, err := topologyIncludesServerNullable(topology, server) |
| if err != nil { |
| return "", warnings, errors.New("getting topology server inclusion: " + err.Error()) |
| } |
| if !topoIncludesServer { |
| continue |
| } |
| } |
| remapText := "" |
| if *ds.Type == tc.DSTypeAnyMap { |
| if ds.RemapText == nil { |
| warnings = append(warnings, "ds '"+*ds.XMLID+"' is ANY_MAP, but has no remap text - skipping") |
| continue |
| } |
| remapText = *ds.RemapText + "\n" |
| textLines = append(textLines, remapText) |
| continue |
| } |
| |
| requestFQDNs, err := getDSRequestFQDNs(&ds, dsRegexes[tc.DeliveryServiceName(*ds.XMLID)], server, cdnDomain) |
| if err != nil { |
| warnings = append(warnings, "error getting ds '"+*ds.XMLID+"' request fqdns, skipping! Error: "+err.Error()) |
| continue |
| } |
| |
| for _, requestFQDN := range requestFQDNs { |
| remapLines, err := makeEdgeDSDataRemapLines(ds, requestFQDN, server, cdnDomain) |
| if err != nil { |
| warnings = append(warnings, "DS '"+*ds.XMLID+"' - skipping! : "+err.Error()) |
| continue |
| } |
| |
| for _, line := range remapLines { |
| profileremapConfigParams := []tc.Parameter{} |
| if ds.ProfileID != nil { |
| profileremapConfigParams = profilesRemapConfigParams[*ds.ProfileID] |
| } |
| remapWarns := []string{} |
| dsLines := RemapLines{} |
| dsLines, remapWarns, err = buildEdgeRemapLine(atsMajorVersion, server, serverPackageParamData, remapText, ds, line.From, line.To, profileremapConfigParams, cacheGroups, nameTopologies, configDir, opts) |
| warnings = append(warnings, remapWarns...) |
| remapText = dsLines.Text |
| |
| // Add to pre/post remap lines if this is last tier |
| if len(dsLines.Pre) > 0 || len(dsLines.Post) > 0 { |
| preRemapLines = append(preRemapLines, dsLines.Pre...) |
| postRemapLines = append(postRemapLines, dsLines.Post...) |
| } |
| |
| if err != nil { |
| return "", warnings, err |
| } |
| remapText += ` # ds '` + *ds.XMLID + `' topology '` |
| if hasTopology { |
| remapText += topology.Name |
| } |
| remapText += `'` + "\n" |
| } |
| } |
| |
| textLines = append(textLines, remapText) |
| } |
| |
| sort.Strings(preRemapLines) |
| sort.Strings(textLines) |
| sort.Strings(postRemapLines) |
| |
| remapLinesAll := append(preRemapLines, textLines...) |
| remapLinesAll = append(remapLinesAll, postRemapLines...) |
| |
| text := header |
| text += strings.Join(remapLinesAll, "") |
| return text, warnings, nil |
| } |
| |
| type RemapLines struct { |
| Pre []string |
| Text string |
| Post []string |
| } |
| |
| // buildEdgeRemapLine builds the remap line for the given server and delivery service. |
| // The cacheKeyConfigParams map may be nil, if this ds profile had no cache key config params. |
| // Returns the remap line, any warnings, and any error. |
| func buildEdgeRemapLine( |
| atsMajorVersion int, |
| server *Server, |
| pData map[string]string, |
| text string, |
| ds DeliveryService, |
| mapFrom string, |
| mapTo string, |
| remapConfigParams []tc.Parameter, |
| cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable, |
| nameTopologies map[TopologyName]tc.Topology, |
| configDir string, |
| opts *RemapDotConfigOpts, |
| ) (RemapLines, []string, error) { |
| warnings := []string{} |
| remapLines := RemapLines{} |
| |
| // ds = 'remap' in perl |
| mapFrom = strings.Replace(mapFrom, `__http__`, *server.HostName, -1) |
| |
| isLastCache, err := serverIsLastCacheForDS(server, &ds, nameTopologies, cacheGroups) |
| if err != nil { |
| return remapLines, warnings, errors.New("determining if cache is the last tier: " + err.Error()) |
| } |
| |
| // if this remap is going to a parent, use http not https. |
| // cache-to-cache communication inside the CDN is always http (though that's likely to change in the future) |
| if !isLastCache { |
| mapTo = strings.Replace(mapTo, `https://`, `http://`, -1) |
| } |
| |
| text += "map " + mapFrom + " " + mapTo |
| |
| text += strategyDirective(getStrategyName(*ds.XMLID), configDir, opts) |
| |
| if _, hasDSCPRemap := pData["dscp_remap"]; hasDSCPRemap { |
| text += ` @plugin=dscp_remap.so @pparam=` + strconv.Itoa(*ds.DSCP) |
| } else { |
| text += ` @plugin=header_rewrite.so @pparam=dscp/set_dscp_` + strconv.Itoa(*ds.DSCP) + ".config" |
| } |
| |
| if *ds.Topology != "" { |
| topoTxt, err := makeDSTopologyHeaderRewriteTxt(ds, tc.CacheGroupName(*server.Cachegroup), nameTopologies[TopologyName(*ds.Topology)], cacheGroups) |
| if err != nil { |
| return remapLines, warnings, err |
| } |
| text += topoTxt |
| } else if (ds.EdgeHeaderRewrite != nil && *ds.EdgeHeaderRewrite != "") || (ds.ServiceCategory != nil && *ds.ServiceCategory != "") || (ds.MaxOriginConnections != nil && *ds.MaxOriginConnections != 0) { |
| text += ` @plugin=header_rewrite.so @pparam=` + edgeHeaderRewriteConfigFileName(*ds.XMLID) |
| } |
| |
| dsConfigParamsMap := classifyConfigParams(remapConfigParams) |
| |
| if ds.SigningAlgorithm != nil && *ds.SigningAlgorithm != "" { |
| if *ds.SigningAlgorithm == tc.SigningAlgorithmURLSig { |
| text += ` @plugin=url_sig.so @pparam=url_sig_` + *ds.XMLID + ".config" + |
| paramsStringFor(dsConfigParamsMap["url_sig.pparam"], &warnings) |
| } else if *ds.SigningAlgorithm == tc.SigningAlgorithmURISigning { |
| text += ` @plugin=uri_signing.so @pparam=uri_signing_` + *ds.XMLID + ".config" + |
| paramsStringFor(dsConfigParamsMap["uri_signing.pparam"], &warnings) |
| } |
| } |
| |
| // Form the cachekey args string, qstring ignore, then |
| // remap.config then cachekey.config |
| cachekeyArgs := "" |
| |
| if ds.QStringIgnore != nil { |
| if *ds.QStringIgnore == tc.QueryStringIgnoreDropAtEdge { |
| dqsFile := "drop_qstring.config" |
| text += ` @plugin=regex_remap.so @pparam=` + dqsFile |
| } else if *ds.QStringIgnore == tc.QueryStringIgnoreIgnoreInCacheKeyAndPassUp { |
| cachekeyArgs = getQStringIgnoreRemap(atsMajorVersion) |
| } |
| } |
| |
| if len(dsConfigParamsMap) > 0 { |
| cachekeyArgs += cachekeyArgsFor(dsConfigParamsMap, &warnings) |
| } |
| |
| if cachekeyArgs != "" { |
| text += " @plugin=cachekey.so" + cachekeyArgs |
| } |
| |
| // Note: should use full path here? |
| if ds.RegexRemap != nil && *ds.RegexRemap != "" { |
| text += ` @plugin=regex_remap.so @pparam=regex_remap_` + *ds.XMLID + ".config" |
| } |
| |
| rangeReqTxt := "" |
| if ds.RangeRequestHandling != nil { |
| crr := false |
| |
| if *ds.RangeRequestHandling == tc.RangeRequestHandlingBackgroundFetch { |
| rangeReqTxt = " @plugin=background_fetch.so @pparam=--config=bg_fetch.config" + |
| paramsStringFor(dsConfigParamsMap["background_fetch.pparam"], &warnings) |
| } else if *ds.RangeRequestHandling == tc.RangeRequestHandlingSlice && ds.RangeSliceBlockSize != nil { |
| |
| rangeReqTxt = " @plugin=slice.so @pparam=--blockbytes=" + strconv.Itoa(*ds.RangeSliceBlockSize) + |
| paramsStringFor(dsConfigParamsMap["slice.pparam"], &warnings) |
| crr = true |
| } else if *ds.RangeRequestHandling == tc.RangeRequestHandlingCacheRangeRequest { |
| crr = true |
| } |
| |
| if crr { |
| rangeReqTxt += " @plugin=cache_range_requests.so " + |
| paramsStringFor(dsConfigParamsMap["cache_range_requests.pparam"], &warnings) |
| } |
| } |
| |
| remapText := "" |
| if ds.RemapText != nil { |
| remapText = *ds.RemapText |
| } |
| |
| // Temporary hack for moving the range directive into the raw remap text |
| if strings.Contains(remapText, RemapConfigRangeDirective) { |
| remapText = strings.Replace(remapText, RemapConfigRangeDirective, rangeReqTxt, 1) |
| } else { |
| text += rangeReqTxt |
| } |
| |
| if remapText != "" { |
| text += " " + remapText |
| } |
| |
| if ds.FQPacingRate != nil && *ds.FQPacingRate > 0 { |
| text += ` @plugin=fq_pacing.so @pparam=--rate=` + strconv.Itoa(*ds.FQPacingRate) |
| } |
| |
| remapLines.Text = text |
| |
| // Any raw pre or post pend lines? |
| if isLastCache { |
| remapLines.Pre, remapLines.Post = lastPrePostRemapLinesFor(dsConfigParamsMap, *ds.XMLID) |
| } |
| |
| return remapLines, warnings, nil |
| } |
| |
| func strategyDirective(strategyName string, configDir string, opt *RemapDotConfigOpts) string { |
| if !opt.UseStrategies { |
| return "" |
| } |
| if !opt.UseStrategiesCore { |
| return ` @plugin=parent_select.so @pparam=` + filepath.Join(configDir, "strategies.yaml") + ` @pparam=` + strategyName |
| } |
| return ` @strategy=` + strategyName |
| } |
| |
| // makeDSTopologyHeaderRewriteTxt returns the appropriate header rewrite remap line text for the given DS on the given server, and any error. |
| // May be empty, if the DS has no header rewrite for the server's position in the topology. |
| func makeDSTopologyHeaderRewriteTxt(ds DeliveryService, cg tc.CacheGroupName, topology tc.Topology, cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable) (string, error) { |
| placement, err := getTopologyPlacement(cg, topology, cacheGroups, &ds) |
| if err != nil { |
| return "", errors.New("getting topology placement: " + err.Error()) |
| } |
| txt := "" |
| const pluginTxt = ` @plugin=header_rewrite.so @pparam=` |
| if placement.IsFirstCacheTier && ((ds.FirstHeaderRewrite != nil && *ds.FirstHeaderRewrite != "") || (ds.ServiceCategory != nil && *ds.ServiceCategory != "")) { |
| txt += pluginTxt + FirstHeaderRewriteConfigFileName(*ds.XMLID) + ` ` |
| } |
| if placement.IsInnerCacheTier && ((ds.InnerHeaderRewrite != nil && *ds.InnerHeaderRewrite != "") || (ds.ServiceCategory != nil && *ds.ServiceCategory != "")) { |
| txt += pluginTxt + InnerHeaderRewriteConfigFileName(*ds.XMLID) + ` ` |
| } |
| if placement.IsLastCacheTier && ((ds.LastHeaderRewrite != nil && *ds.LastHeaderRewrite != "") || (ds.ServiceCategory != nil && *ds.ServiceCategory != "") || (ds.MaxOriginConnections != nil && *ds.MaxOriginConnections != 0)) { |
| txt += pluginTxt + LastHeaderRewriteConfigFileName(*ds.XMLID) + ` ` |
| } |
| return txt, nil |
| } |
| |
| type remapLine struct { |
| From string |
| To string |
| } |
| |
| // makeEdgeDSDataRemapLines returns the remap lines for the given server and delivery service. |
| // Returns nil, if the given server and ds have no remap lines, i.e. the DS match is not a host regex, or has no origin FQDN. |
| func makeEdgeDSDataRemapLines( |
| ds DeliveryService, |
| requestFQDN string, |
| // dsRegex tc.DeliveryServiceRegex, |
| server *Server, |
| cdnDomain string, |
| ) ([]remapLine, error) { |
| if ds.Protocol == nil { |
| return nil, errors.New("ds had nil protocol") |
| } |
| |
| remapLines := []remapLine{} |
| mapTo := *ds.OrgServerFQDN + "/" |
| |
| portStr := "" |
| if !ds.Type.IsDNS() && server.TCPPort != nil && *server.TCPPort > 0 && *server.TCPPort != 80 { |
| portStr = ":" + strconv.Itoa(*server.TCPPort) |
| } |
| |
| httpsPortStr := "" |
| if !ds.Type.IsDNS() && server.HTTPSPort != nil && *server.HTTPSPort > 0 && *server.HTTPSPort != 443 { |
| httpsPortStr = ":" + strconv.Itoa(*server.HTTPSPort) |
| } |
| |
| mapFromHTTP := "http://" + requestFQDN + portStr + "/" |
| mapFromHTTPS := "https://" + requestFQDN + httpsPortStr + "/" |
| |
| if *ds.Protocol == tc.DSProtocolHTTP || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS { |
| remapLines = append(remapLines, remapLine{From: mapFromHTTP, To: mapTo}) |
| } |
| if *ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == tc.DSProtocolHTTPToHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS { |
| remapLines = append(remapLines, remapLine{From: mapFromHTTPS, To: mapTo}) |
| } |
| |
| return remapLines, nil |
| } |
| |
| func edgeHeaderRewriteConfigFileName(dsName string) string { |
| return "hdr_rw_" + dsName + ".config" |
| } |
| |
| func midHeaderRewriteConfigFileName(dsName string) string { |
| return "hdr_rw_mid_" + dsName + ".config" |
| } |
| |
| // getQStringIgnoreRemap returns the remap, whether cachekey was added. |
| func getQStringIgnoreRemap(atsMajorVersion int) string { |
| if atsMajorVersion < 7 { |
| log.Errorf("Unsupport version of ats found %v", atsMajorVersion) |
| return "" |
| } |
| return ` @pparam=--separator= @pparam=--remove-all-params=true @pparam=--remove-path=true @pparam=--capture-prefix-uri=/^([^?]*)/$1/` |
| } |
| |
| // makeServerPackageParamData returns a map[paramName]paramVal for this server, config file 'package'. |
| // Returns the param data, and any warnings |
| func makeServerPackageParamData(server *Server, serverParams []tc.Parameter) (map[string]string, []string) { |
| warnings := []string{} |
| |
| serverPackageParamData := map[string]string{} |
| for _, param := range serverParams { |
| if param.ConfigFile != "package" { // TODO put in const |
| continue |
| } |
| if param.Name == "location" { // TODO put in const |
| continue |
| } |
| |
| paramName := param.Name |
| // some files have multiple lines with the same key... handle that with param id. |
| if _, ok := serverPackageParamData[param.Name]; ok { |
| paramName += "__" + strconv.Itoa(param.ID) |
| } |
| paramValue := param.Value |
| if paramValue == "STRING __HOSTNAME__" { |
| paramValue = *server.HostName + "." + *server.DomainName // TODO strings.Replace to replace all anywhere, instead of just an exact match? |
| } |
| |
| if val, ok := serverPackageParamData[paramName]; ok { |
| if val < paramValue { |
| warnings = append(warnings, "got multiple parameters for server package name '"+paramName+"' - ignoring '"+paramValue+"'") |
| continue |
| } else { |
| warnings = append(warnings, "got multiple parameters for server package name '"+paramName+"' - ignoring '"+val+"'") |
| } |
| } |
| serverPackageParamData[paramName] = paramValue |
| } |
| return serverPackageParamData, warnings |
| } |
| |
| // remapFilterDSes filters Delivery Services to be used to generate remap.config for the given server. |
| // Returned DSes are guaranteed to have a non-nil XMLID, Type, DSCP, ID, Active, and Topology. |
| // If a DS has a nil Topology, OrgServerFQDN, FirstHeaderRewrite, InnerHeaderRewrite, or LastHeaderRewrite, "" is assigned. |
| // Returns the filtered delivery services, and any warnings |
| func remapFilterDSes(server *Server, dss []DeliveryServiceServer, dses []DeliveryService) ([]DeliveryService, []string) { |
| warnings := []string{} |
| isMid := strings.HasPrefix(server.Type, string(tc.CacheTypeMid)) |
| |
| serverIDs := map[int]struct{}{} |
| if !isMid { |
| // mids use all servers, so pass empty=all. Edges only use this current server |
| serverIDs[*server.ID] = struct{}{} |
| } |
| |
| dsIDs := map[int]struct{}{} |
| for _, ds := range dses { |
| if ds.ID == nil { |
| // TODO log error? |
| continue |
| } |
| dsIDs[*ds.ID] = struct{}{} |
| } |
| |
| dsServers := filterDSS(dss, dsIDs, serverIDs) |
| |
| dssMap := map[int]map[int]struct{}{} // set of map[dsID][serverID] |
| for _, dss := range dsServers { |
| if dssMap[dss.DeliveryService] == nil { |
| dssMap[dss.DeliveryService] = map[int]struct{}{} |
| } |
| dssMap[dss.DeliveryService][dss.Server] = struct{}{} |
| } |
| |
| useInactive := false |
| if !isMid { |
| // mids get inactive DSes, edges don't. This is how it's always behaved, not necessarily how it should. |
| useInactive = true |
| } |
| |
| filteredDSes := []DeliveryService{} |
| for _, ds := range dses { |
| if ds.Topology == nil { |
| ds.Topology = util.StrPtr("") |
| } |
| if ds.OrgServerFQDN == nil { |
| ds.OrgServerFQDN = util.StrPtr("") |
| } |
| if ds.FirstHeaderRewrite == nil { |
| ds.FirstHeaderRewrite = util.StrPtr("") |
| } |
| if ds.InnerHeaderRewrite == nil { |
| ds.InnerHeaderRewrite = util.StrPtr("") |
| } |
| if ds.LastHeaderRewrite == nil { |
| ds.LastHeaderRewrite = util.StrPtr("") |
| } |
| if ds.XMLID == nil { |
| warnings = append(warnings, "got Delivery Service with nil XMLID, skipping!") |
| continue |
| } else if ds.Type == nil { |
| warnings = append(warnings, "got Delivery Service '"+*ds.XMLID+"' with nil Type, skipping!") |
| continue |
| } else if ds.DSCP == nil { |
| warnings = append(warnings, "got Delivery Service '"+*ds.XMLID+"' with nil DSCP, skipping!") |
| continue |
| } else if ds.ID == nil { |
| warnings = append(warnings, "got Delivery Service '"+*ds.XMLID+"' with nil ID, skipping!") |
| continue |
| } else if ds.Active == nil { |
| warnings = append(warnings, "got Delivery Service '"+*ds.XMLID+"' with nil Active, skipping!") |
| continue |
| } else if _, ok := dssMap[*ds.ID]; !ok && *ds.Topology == "" { |
| continue // normal, not an error, this DS just isn't assigned to our Cache |
| } else if !useInactive && !*ds.Active { |
| continue // normal, not an error, DS just isn't active and we aren't including inactive DSes |
| } |
| filteredDSes = append(filteredDSes, ds) |
| } |
| return filteredDSes, warnings |
| } |
| |
| // makeDSProfilesConfigParams returns a map[ProfileID][ParamName]ParamValue for the cache key params for each profile. |
| // Returns the params, any warnings, and any error. |
| func makeDSProfilesConfigParams(server *Server, dses []DeliveryService, remapConfigParams []tc.Parameter) (map[int][]tc.Parameter, []string, error) { |
| warnings := []string{} |
| dsConfigParamsWithProfiles, err := tcParamsToParamsWithProfiles(remapConfigParams) |
| if err != nil { |
| return nil, warnings, errors.New("decoding cache key parameter profiles: " + err.Error()) |
| } |
| |
| configParamsWithProfilesMap := parameterWithProfilesToMap(dsConfigParamsWithProfiles) |
| |
| dsProfileNamesToIDs := map[string]int{} |
| for _, ds := range dses { |
| if ds.ProfileID == nil || ds.ProfileName == nil { |
| continue // TODO log |
| } |
| dsProfileNamesToIDs[*ds.ProfileName] = *ds.ProfileID |
| } |
| |
| dsProfilesConfigParams := map[int][]tc.Parameter{} |
| for _, param := range configParamsWithProfilesMap { |
| for dsProfileName, dsProfileID := range dsProfileNamesToIDs { |
| if _, ok := param.ProfileNames[dsProfileName]; ok { |
| if _, ok := dsProfilesConfigParams[dsProfileID]; !ok { |
| dsProfilesConfigParams[dsProfileID] = []tc.Parameter{} |
| } |
| dsProfilesConfigParams[dsProfileID] = append(dsProfilesConfigParams[dsProfileID], param.Parameter) |
| } |
| } |
| } |
| return dsProfilesConfigParams, warnings, nil |
| } |
| |
| type deliveryServiceRegexesSortByTypeThenSetNum []tc.DeliveryServiceRegex |
| |
| func (r deliveryServiceRegexesSortByTypeThenSetNum) Len() int { return len(r) } |
| func (r deliveryServiceRegexesSortByTypeThenSetNum) Less(i, j int) bool { |
| if rc := strings.Compare(r[i].Type, r[j].Type); rc != 0 { |
| return rc < 0 |
| } |
| return r[i].SetNumber < r[j].SetNumber |
| } |
| func (r deliveryServiceRegexesSortByTypeThenSetNum) Swap(i, j int) { r[i], r[j] = r[j], r[i] } |
| |
| func makeDSRegexMap(regexes []tc.DeliveryServiceRegexes) map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex { |
| dsRegexMap := map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex{} |
| for _, dsRegex := range regexes { |
| sort.Sort(deliveryServiceRegexesSortByTypeThenSetNum(dsRegex.Regexes)) |
| dsRegexMap[tc.DeliveryServiceName(dsRegex.DSName)] = dsRegex.Regexes |
| } |
| return dsRegexMap |
| } |
| |
| type keyVal struct { |
| Key string |
| Val string |
| } |
| |
| type keyVals []keyVal |
| |
| func (ks keyVals) Len() int { return len(ks) } |
| func (ks keyVals) Swap(i, j int) { ks[i], ks[j] = ks[j], ks[i] } |
| func (ks keyVals) Less(i, j int) bool { |
| if ks[i].Key != ks[j].Key { |
| return ks[i].Key < ks[j].Key |
| } |
| return ks[i].Val < ks[j].Val |
| } |
| |
| // getDSRequestFQDNs returns the FQDNs that clients will request from the edge. |
| func getDSRequestFQDNs(ds *DeliveryService, regexes []tc.DeliveryServiceRegex, server *Server, cdnDomain string) ([]string, error) { |
| if server.HostName == nil { |
| return nil, errors.New("server missing hostname") |
| } |
| |
| fqdns := []string{} |
| for _, dsRegex := range regexes { |
| if tc.DSMatchType(dsRegex.Type) != tc.DSMatchTypeHostRegex || ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" { |
| continue |
| } |
| if dsRegex.Pattern == "" { |
| return nil, errors.New("ds missing regex pattern") |
| } |
| if ds.Protocol == nil { |
| return nil, errors.New("ds missing protocol") |
| } |
| if cdnDomain == "" { |
| return nil, errors.New("ds missing domain") |
| } |
| |
| hostRegex := dsRegex.Pattern |
| fqdn := hostRegex |
| |
| if strings.HasSuffix(hostRegex, `.*`) { |
| re := hostRegex |
| re = strings.Replace(re, `\`, ``, -1) |
| re = strings.Replace(re, `.*`, ``, -1) |
| |
| hName := *server.HostName |
| if ds.Type.IsDNS() { |
| if ds.RoutingName == nil { |
| return nil, errors.New("ds is dns, but missing routing name") |
| } |
| hName = *ds.RoutingName |
| } |
| |
| fqdn = hName + re + cdnDomain |
| } |
| fqdns = append(fqdns, fqdn) |
| } |
| return fqdns, nil |
| } |
| |
| func serverIsLastCacheForDS(server *Server, ds *DeliveryService, topologies map[TopologyName]tc.Topology, cacheGroups map[tc.CacheGroupName]tc.CacheGroupNullable) (bool, error) { |
| if ds.Topology != nil && strings.TrimSpace(*ds.Topology) != "" { |
| if server.Cachegroup == nil { |
| return false, errors.New("Server has no CacheGroup") |
| } |
| topology, ok := topologies[TopologyName(*ds.Topology)] |
| if !ok { |
| return false, errors.New("DS topology '" + *ds.Topology + "' not found in topologies") |
| } |
| topoPlacement, err := getTopologyPlacement(tc.CacheGroupName(*server.Cachegroup), topology, cacheGroups, ds) |
| if err != nil { |
| return false, errors.New("getting topology placement: " + err.Error()) |
| } |
| return topoPlacement.IsLastCacheTier, nil |
| } |
| |
| return noTopologyServerIsLastCacheForDS(server, ds), nil |
| } |
| |
| // noTopologyServerIsLastCacheForDS returns whether the server is the last tier for the DS, if the DS has no Topology. |
| // This helper MUST NOT be called if the DS has a Topology. It does not check. |
| func noTopologyServerIsLastCacheForDS(server *Server, ds *DeliveryService) bool { |
| return strings.HasPrefix(server.Type, tc.MidTypePrefix) || !ds.Type.UsesMidCache() |
| } |