blob: 95528eb660159e48d8282b2dd05cdf3609575829 [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 (
"errors"
"strconv"
"strings"
)
// ParentAbstraction contains all the data necessary to build either parent.config or strategies.yaml.
type ParentAbstraction struct {
Services []*ParentAbstractionService
// Peers is the list of peer proxy caches to be used in a strategy peering group.
// a cache will only have one set of peers for potential use in all delivery services.
Peers []*ParentAbstractionServiceParent
}
// ParentAbstractionService represents a single delivery service's parent data.
// For parent.config, this becomes a single dest_domain= line.
// For strategies, this becomes a strategy along with its corresponding groups and hosts.
type ParentAbstractionService struct {
// Name is a unique name for the service.
// It can be anything unique, but should probably be the Traffic ops Delivery Service name.
Name string
// Comment is a text comment about the service, not including the comment syntax (e.g. # or //).
// Should be empty if !opt.AddComments.
Comment string
// DestDomain is the FQDN of the remap.config target.
// Becomes parent.config dest_domain directive
// Becomes strategies.yaml TODO
DestDomain string
// Port is the port of the remap.config target,
// which MUST be valid, and is implicitly 80 for http targets and 443 for https targets.
// Becomes parent.config port directive
// Becomes strategies.yaml TODO
Port int
// Parents is the list of parents, either parent proxy caches or origins.
// This is a sorted array. Parents will be inserted into the config file in the order they appear.
// Becomes parent.config parent= directive members
// Becomes strategies.yaml TODO
Parents []*ParentAbstractionServiceParent
// Parents is the list of secondary parents, either parent proxy caches or origins,
// to be used if the primary parents fail. See SecondaryMode.
// Becomes parent.config secondary_parent= directive members
// Becomes strategies.yaml TODO
SecondaryParents []*ParentAbstractionServiceParent
// SecondaryMode is how to try SecondaryParents if primary Parents fail.
// Becomes parent.config secondary_mode directive
// Becomes strategies.yaml TODO
SecondaryMode ParentAbstractionServiceParentSecondaryMode
// CachePeerResult is used only when the RetryPolicy is set to
// 'consistent_hash' and the SecondaryMode is set to 'peering'.
// In the case that it's used and set to 'true', query results
// from peer caches will not be cached locally.
CachePeerResult bool
// GoDirect is whether to go direct to parents via normal HTTP requests.
// False means to make proxy requests to the parents.
// Becomes parent.config go_direct and parent_is_proxy directives
// Becomes strategies.yaml TODO
GoDirect bool
// IgnoreQueryStringInParentSelection is whether to use the query string of the request
// when selecting a parent, e.g. via Consistent Hash.
// Becomes parent.config qstring directive
// Becomes strategies.yaml TODO
IgnoreQueryStringInParentSelection bool
// MarkdownResponseCodes is the list of HTTP response codes from the parent
// to consider as errors and mark the parent as unhealthy. Typically 5xx codes.
// Becomes parent.config unavailable_server_retry_responses directive
// Becomes strategies.yaml TODO
MarkdownResponseCodes []int
// ErrorResponseCodes is the list of HTTP response codes from the parent
// to consider as errors, but NOT mark the parent unhealthy. Typically 4xx codes.
// Becomes parent.config unavailable_server_retry_responses directive
// Becomes strategies.yaml TODO
ErrorResponseCodes []int
// MaxSimpleRetries is the maximum number of non-markdown errors to attempt
// before returning the error to the client. See ErrorResponseCodes
// Becomes parent.config max_simple_retries
// Becomes strategies.yaml TODO
MaxSimpleRetries int
// MaxMarkdownRetries is the maximum number of markdown errors to attempt
// before returning the error to the client. See MarkdownResponseCodes
// Becomes parent.config max_unavailable_server_retries
// Becomes strategies.yaml TODO
MaxMarkdownRetries int
// RetryPolicy is how to retry primary versus secondary parents.
// Becomes parent.config round_robin directive
// Becomes strategies.yaml TODO
RetryPolicy ParentAbstractionServiceRetryPolicy
// Weight is the weight of this parent relative to other parents in consistent hash (and potentially other non-sequential) parent selection. The default is 0.999
// Becomes parent.config weight directive
// Becomes strategies.yaml TODO
Weight float64
}
// ParentAbstractionServices implements sort.Interface
type ParentAbstractionServices []*ParentAbstractionService
func (ps ParentAbstractionServices) Len() int { return len(ps) }
func (ps ParentAbstractionServices) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
func (ps ParentAbstractionServices) Less(i, j int) bool {
if ps[i].DestDomain != ps[j].DestDomain {
return ps[i].DestDomain < ps[j].DestDomain
}
return ps[i].Port < ps[j].Port
}
type ParentAbstractionServiceParentSecondaryMode string
const ParentAbstractionServiceParentSecondaryModeExhaust = ParentAbstractionServiceParentSecondaryMode("exhaust")
const ParentAbstractionServiceParentSecondaryModeAlternate = ParentAbstractionServiceParentSecondaryMode("alternate")
const ParentAbstractionServiceParentSecondaryModePeering = ParentAbstractionServiceParentSecondaryMode("peering")
const ParentAbstractionServiceParentSecondaryModeInvalid = ParentAbstractionServiceParentSecondaryMode("")
const ParentAbstractionServiceParentSecondaryModeDefault = ParentAbstractionServiceParentSecondaryModeAlternate
// ToParentDotConfigVal returns the ATS parent.config secondary_mode= value for the enum.
// If the mode is invalid, ParentAbstractionServiceParentSecondaryModeDefault is returned without error.
func (mo ParentAbstractionServiceParentSecondaryMode) ToParentDotConfigVal() string {
switch mo {
case ParentAbstractionServiceParentSecondaryModeExhaust:
return "2"
case ParentAbstractionServiceParentSecondaryModeAlternate:
return "1"
default:
return ParentAbstractionServiceParentSecondaryModeDefault.ToParentDotConfigVal()
}
}
type ParentAbstractionServiceRetryPolicy string
const ParentAbstractionServiceRetryPolicyRoundRobinIP = ParentAbstractionServiceRetryPolicy("round_robin_ip")
const ParentAbstractionServiceRetryPolicyRoundRobinStrict = ParentAbstractionServiceRetryPolicy("round_robin_strict")
const ParentAbstractionServiceRetryPolicyFirst = ParentAbstractionServiceRetryPolicy("first")
const ParentAbstractionServiceRetryPolicyLatched = ParentAbstractionServiceRetryPolicy("latched")
const ParentAbstractionServiceRetryPolicyConsistentHash = ParentAbstractionServiceRetryPolicy("consistent_hash")
const ParentAbstractionServiceRetryPolicyInvalid = ParentAbstractionServiceRetryPolicy("")
const DefaultParentAbstractionServiceRetryPolicy = ParentAbstractionServiceRetryPolicyConsistentHash
func ParentSelectAlgorithmToParentAbstractionServiceRetryPolicy(alg string) ParentAbstractionServiceRetryPolicy {
switch strings.TrimSpace(strings.ToLower(alg)) {
case "true":
return ParentAbstractionServiceRetryPolicyRoundRobinIP
case "strict":
return ParentAbstractionServiceRetryPolicyRoundRobinStrict
case "false":
return ParentAbstractionServiceRetryPolicyFirst
case "consistent_hash":
return ParentAbstractionServiceRetryPolicyConsistentHash
case "latched":
return ParentAbstractionServiceRetryPolicyLatched
default:
return ParentAbstractionServiceRetryPolicyInvalid
}
}
// ToParentDotConfigFormat returns the ATS parent.config round_robin= value for the policy.
// If the policy is invalid, the default is returned without error.
func (po ParentAbstractionServiceRetryPolicy) ToParentDotConfigFormat() string {
switch po {
case ParentAbstractionServiceRetryPolicyRoundRobinIP:
return "true"
case ParentAbstractionServiceRetryPolicyRoundRobinStrict:
return "strict"
case ParentAbstractionServiceRetryPolicyFirst:
return "false"
case ParentAbstractionServiceRetryPolicyLatched:
return "latched"
case ParentAbstractionServiceRetryPolicyConsistentHash:
return "consistent_hash"
default:
return "consistent_hash"
}
}
// ParentSelectParamQStringHandlingToBool returns whether the param is to use the query string in the parent select algorithm or not.
// If the parameter value is not valid, returns nil.
func ParentSelectParamQStringHandlingToBool(paramVal string) *bool {
switch strings.TrimSpace(strings.ToLower(paramVal)) {
case "consider":
v := true
return &v
case "ignore":
v := false
return &v
}
return nil
}
type ParentAbstractionServiceParent struct {
// FQDN is the parent FQDN that ATS will use. Note this may be an IP.
FQDN string
Port int
Weight float64
}
// Key returns a unique key that can be used to compare parents for equality.
func (sp ParentAbstractionServiceParent) Key() string {
return sp.FQDN + ":" + strconv.Itoa(sp.Port)
}
func RemoveParentDuplicates(inputs []*ParentAbstractionServiceParent, seens map[string]struct{}) ([]*ParentAbstractionServiceParent, map[string]struct{}) {
if seens == nil {
seens = make(map[string]struct{})
}
uniques := []*ParentAbstractionServiceParent{}
for _, input := range inputs {
key := input.Key()
if _, ok := seens[key]; !ok {
uniques = append(uniques, input)
seens[key] = struct{}{}
}
}
return uniques, seens
}
func ParseRetryResponses(resp string) ([]int, error) {
resp = strings.TrimSpace(resp)
if len(resp) > 2 && resp[0] == '"' {
resp = resp[1 : len(resp)-1]
}
codes := []int{}
codeStrs := strings.Split(resp, ",")
for _, codeStr := range codeStrs {
codeStr = strings.TrimSpace(codeStr)
if codeStr == "" {
continue
}
code, err := strconv.Atoi(codeStr)
if err != nil {
return nil, errors.New("malformed")
}
codes = append(codes, code)
}
return codes, nil
}
var DefaultSimpleRetryCodes = []int{404}
var DefaultUnavailableServerRetryCodes = []int{503}
const DefaultIgnoreQueryStringInParentSelection = false
func parentAbstractionToParentDotConfig(pa *ParentAbstraction, opt *ParentConfigOpts, atsMajorVersion int) (string, []string, error) {
warnings := []string{}
txt := ""
// parent.config dest_domain directives must be unique.
// This is the "duplicate origin problem"
processedOriginsToDSNames := map[string]string{}
for _, svc := range pa.Services {
if existingDS, ok := processedOriginsToDSNames[svc.DestDomain]; ok {
warnings = append(warnings, "duplicate origin! DS '"+svc.Name+"' and '"+existingDS+"' share origin '"+svc.DestDomain+"': skipping '"+svc.Name+"'!")
continue
}
svcLine, svcWarns, err := svc.ToParentDotConfigLine(opt, atsMajorVersion)
warnings = append(warnings, svcWarns...)
if err != nil {
// TODO add DS name
// TODO don't error? No single delivery service should be able to break others.
return "", warnings, errors.New("creating parent.config line from service: " + err.Error())
}
txt += svcLine + "\n"
processedOriginsToDSNames[svc.DestDomain] = svc.Name
}
return txt, warnings, nil
}
func (svc *ParentAbstractionService) ToParentDotConfigLine(opt *ParentConfigOpts, atsMajorVersion int) (string, []string, error) {
warnings := []string{}
txt := ""
if opt.AddComments && svc.Comment != "" {
txt += LineCommentParentDotConfig + " " + svc.Comment + "\n"
}
txt += `dest_domain=` + svc.DestDomain
if svc.Port != 0 {
txt += ` port=` + strconv.Itoa(svc.Port)
}
if atsMajorVersion >= 6 && svc.RetryPolicy == ParentAbstractionServiceRetryPolicyConsistentHash && len(svc.SecondaryParents) > 0 {
// TODO add quotes
if len(svc.Parents) > 0 {
txt += ` parent="` + ParentAbstractionServiceParentsToParentDotConfigLine(svc.Parents) + `"`
}
if len(svc.SecondaryParents) > 0 {
txt += ` secondary_parent="` + ParentAbstractionServiceParentsToParentDotConfigLine(svc.SecondaryParents) + `"`
}
txt += ` secondary_mode=` + svc.SecondaryMode.ToParentDotConfigVal()
} else {
parents := []*ParentAbstractionServiceParent{}
for _, pa := range svc.Parents {
parents = append(parents, pa)
}
for _, pa := range svc.SecondaryParents {
parents = append(parents, pa)
}
if len(parents) > 0 {
txt += ` parent="` + ParentAbstractionServiceParentsToParentDotConfigLine(parents) + `"`
}
}
txt += ` round_robin=` + svc.RetryPolicy.ToParentDotConfigFormat()
txt += ` go_direct=` + strconv.FormatBool(svc.GoDirect)
txt += ` qstring=`
if !svc.IgnoreQueryStringInParentSelection {
txt += `consider`
} else {
txt += `ignore`
}
txt += ` parent_is_proxy=` + strconv.FormatBool(!svc.GoDirect)
if svc.MaxSimpleRetries > 0 && svc.MaxMarkdownRetries > 0 {
txt += ` parent_retry=both`
} else if svc.MaxSimpleRetries > 0 {
txt += ` parent_retry=simple_retry`
} else if svc.MaxMarkdownRetries > 0 {
txt += ` parent_retry=unavailable_server_retry`
}
if svc.MaxSimpleRetries > 0 {
txt += ` max_simple_retries=` + strconv.Itoa(svc.MaxSimpleRetries)
}
if svc.MaxMarkdownRetries > 0 {
txt += ` max_unavailable_server_retries=` + strconv.Itoa(svc.MaxMarkdownRetries)
}
if len(svc.ErrorResponseCodes) > 0 {
if atsMajorVersion >= 9 {
txt += ` simple_server_retry_responses="` + strings.Join(intsToStrs(svc.ErrorResponseCodes), `,`) + `"`
} else {
warnings = append(warnings, "Service '"+svc.Name+"' had simple retry codes '"+strings.Join(intsToStrs(svc.ErrorResponseCodes), ",")+"' but ATS version "+strconv.Itoa(atsMajorVersion)+" < 9 does not support custom simple retry codes, omitting!")
}
}
if len(svc.MarkdownResponseCodes) > 0 {
txt += ` unavailable_server_retry_responses="` + strings.Join(intsToStrs(svc.MarkdownResponseCodes), `,`) + `"`
}
// txt += ` unavailable_server_retry_responses=` + unavailableServerRetryResponses
return txt, warnings, nil
}
func intsToStrs(is []int) []string {
strs := []string{}
for _, i := range is {
strs = append(strs, strconv.Itoa(i))
}
return strs
}
func (pa *ParentAbstractionServiceParent) ToParentDotConfigFormat() string {
return pa.FQDN + ":" + strconv.Itoa(pa.Port) + "|" + strconv.FormatFloat(pa.Weight, 'f', -1, 64)
}
const ParentDotConfigParentSeparator = `;`
func ParentAbstractionServiceParentsToParentDotConfigLine(parents []*ParentAbstractionServiceParent) string {
parentStrs := []string{}
for _, parent := range parents {
parentStrs = append(parentStrs, parent.ToParentDotConfigFormat())
}
return strings.Join(parentStrs, ParentDotConfigParentSeparator)
}