blob: cbd23aa99ddd033271812d33aa6ae40b83883b89 [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"
"path/filepath"
"strings"
"github.com/apache/trafficcontrol/lib/go-tc"
)
type CfgMeta struct {
Name string
Path string
}
// ConfigFilesListOpts contains settings to configure generation options.
type ConfigFilesListOpts struct {
}
// MakeMetaObj returns the list of config files, any warnings, and any errors.
func MakeConfigFilesList(
configDir string,
server *Server,
serverParams []tc.Parameter,
deliveryServices []DeliveryService,
deliveryServiceServers []DeliveryServiceServer,
globalParams []tc.Parameter,
cacheGroupArr []tc.CacheGroupNullable,
topologies []tc.Topology,
opt *ConfigFilesListOpts,
) ([]CfgMeta, []string, error) {
if opt == nil {
opt = &ConfigFilesListOpts{}
}
warnings := []string{}
if server.Cachegroup == nil {
return nil, warnings, errors.New("this server missing Cachegroup")
} else if server.CachegroupID == nil {
return nil, warnings, errors.New("this server missing CachegroupID")
} else if server.TCPPort == nil {
return nil, warnings, errors.New("server missing TCPPort")
} else if server.HostName == nil {
return nil, warnings, errors.New("server missing HostName")
} else if server.CDNID == nil {
return nil, warnings, errors.New("server missing CDNID")
} else if server.CDNName == nil {
return nil, warnings, errors.New("server missing CDNName")
} else if server.ID == nil {
return nil, warnings, errors.New("server missing ID")
} else if len(server.ProfileNames) == 0 {
return nil, warnings, errors.New("server missing Profile")
}
atsMajorVer, verWarns := getATSMajorVersion(serverParams)
warnings = append(warnings, verWarns...)
dses, dsWarns := filterConfigFileDSes(server, deliveryServices, deliveryServiceServers)
warnings = append(warnings, dsWarns...)
locationParams := getLocationParams(serverParams)
uriSignedDSes, signDSWarns := getURISignedDSes(dses)
warnings = append(warnings, signDSWarns...)
configFiles := []CfgMeta{}
if locationParams["remap.config"].Path != "" {
configLocation := locationParams["remap.config"].Path
for _, ds := range uriSignedDSes {
cfgName := "uri_signing_" + string(ds) + ".config"
// If there's already a parameter for it, don't clobber it. The user may wish to override the location.
if _, ok := locationParams[cfgName]; !ok {
p := locationParams[cfgName]
p.Name = cfgName
p.Path = configLocation
locationParams[cfgName] = p
}
}
}
locationParamsFor:
for cfgFile, cfgParams := range locationParams {
if strings.HasSuffix(cfgFile, ".config") {
dsConfigFilePrefixes := []string{
"hdr_rw_mid_", // must come before hdr_rw_, to avoid thinking we have a "hdr_rw_" with a ds of "mid_x"
"hdr_rw_",
"regex_remap_",
"url_sig_",
"uri_signing_",
}
prefixFor:
for _, prefix := range dsConfigFilePrefixes {
if strings.HasPrefix(cfgFile, prefix) {
dsName := strings.TrimSuffix(strings.TrimPrefix(cfgFile, prefix), ".config")
if _, ok := dses[tc.DeliveryServiceName(dsName)]; !ok {
warnings = append(warnings, "server profile had 'location' Parameter '"+cfgFile+"', but delivery Service '"+dsName+"' is not assigned to this Server! Not including in meta config!")
continue locationParamsFor
}
break prefixFor // if it has a prefix, don't check the next prefix. This is important for hdr_rw_mid_, which will match hdr_rw_ and result in a "ds name" of "mid_x" if we don't continue here.
}
}
}
atsCfg := CfgMeta{
Name: cfgParams.Name,
Path: cfgParams.Path,
}
configFiles = append(configFiles, atsCfg)
}
configFiles, configDirWarns, err := addMetaObjConfigDir(configFiles, configDir, server, locationParams, uriSignedDSes, dses, cacheGroupArr, topologies, atsMajorVer)
warnings = append(warnings, configDirWarns...)
return configFiles, warnings, err
}
// addMetaObjConfigDir takes the Meta Object generated from TO data, and the ATS config directory
// and prepends the config directory to all relative paths,
// and creates MetaObj entries for all required config files which have no location parameter.
// If configDir is empty and any location Parameters have relative paths, returns an error.
// Returns the amended config files list, any warnings, and any error.
func addMetaObjConfigDir(
configFiles []CfgMeta,
configDir string,
server *Server,
locationParams map[string]configProfileParams, // map[configFile]params; 'location' and 'URL' Parameters on serverHostName's Profile
uriSignedDSes []tc.DeliveryServiceName,
dses map[tc.DeliveryServiceName]DeliveryService,
cacheGroupArr []tc.CacheGroupNullable,
topologies []tc.Topology,
atsMajorVer int,
) ([]CfgMeta, []string, error) {
warnings := []string{}
if server.Cachegroup == nil {
return nil, warnings, errors.New("server missing Cachegroup")
}
cacheGroups, err := makeCGMap(cacheGroupArr)
if err != nil {
return nil, warnings, errors.New("making CG map: " + err.Error())
}
// Note there may be multiple files with the same name in different directories.
configFilesM := map[string][]CfgMeta{} // map[fileShortName]CfgMeta
for _, fi := range configFiles {
configFilesM[fi.Name] = append(configFilesM[fi.Path], fi)
}
// add all strictly required files, all of which should be in the base config directory.
// If they don't exist, create them.
// If they exist with a relative path, prepend configDir.
// If any exist with a relative path, or don't exist, and configDir is empty, return an error.
for _, fileName := range requiredFiles(atsMajorVer) {
if _, ok := configFilesM[fileName]; ok {
continue
}
if configDir == "" {
return nil, warnings, errors.New("required file '" + fileName + "' has no location Parameter, and ATS config directory not found.")
}
configFilesM[fileName] = []CfgMeta{{
Name: fileName,
Path: configDir,
}}
}
for fileName, fis := range configFilesM {
newFis := []CfgMeta{}
for _, fi := range fis {
if !filepath.IsAbs(fi.Path) {
if configDir == "" {
return nil, warnings, errors.New("file '" + fileName + "' has location Parameter with relative path '" + fi.Path + "', but ATS config directory was not found.")
}
absPath := filepath.Join(configDir, fi.Path)
fi.Path = absPath
}
newFis = append(newFis, fi)
}
configFilesM[fileName] = newFis
}
nameTopologies := makeTopologyNameMap(topologies)
for _, ds := range dses {
if ds.XMLID == nil {
warnings = append(warnings, "got Delivery Service with nil XMLID - not considering!")
continue
}
err := error(nil)
// Note we log errors, but don't return them.
// If an individual DS has an error, we don't want to break the rest of the CDN.
if ds.Topology != nil && *ds.Topology != "" {
topology := nameTopologies[TopologyName(*ds.Topology)]
placement, err := getTopologyPlacement(tc.CacheGroupName(*server.Cachegroup), topology, cacheGroups, &ds)
if err != nil {
return nil, warnings, errors.New("getting topology placement: " + err.Error())
}
if placement.IsFirstCacheTier {
if (ds.FirstHeaderRewrite != nil && *ds.FirstHeaderRewrite != "") || ds.MaxOriginConnections != nil || ds.ServiceCategory != nil {
fileName := FirstHeaderRewriteConfigFileName(*ds.XMLID)
if configFilesM, err = ensureConfigFile(configFilesM, fileName, configDir); err != nil {
warnings = append(warnings, "ensuring config file '"+fileName+"': "+err.Error())
}
}
}
if placement.IsInnerCacheTier {
if (ds.InnerHeaderRewrite != nil && *ds.InnerHeaderRewrite != "") || ds.MaxOriginConnections != nil || ds.ServiceCategory != nil {
fileName := InnerHeaderRewriteConfigFileName(*ds.XMLID)
if configFilesM, err = ensureConfigFile(configFilesM, fileName, configDir); err != nil {
warnings = append(warnings, "ensuring config file '"+fileName+"': "+err.Error())
}
}
}
if placement.IsLastCacheTier {
if (ds.LastHeaderRewrite != nil && *ds.LastHeaderRewrite != "") || ds.MaxOriginConnections != nil || ds.ServiceCategory != nil {
fileName := LastHeaderRewriteConfigFileName(*ds.XMLID)
if configFilesM, err = ensureConfigFile(configFilesM, fileName, configDir); err != nil {
warnings = append(warnings, "ensuring config file '"+fileName+"': "+err.Error())
}
}
}
} else if strings.HasPrefix(server.Type, tc.EdgeTypePrefix) {
if (ds.EdgeHeaderRewrite != nil || ds.MaxOriginConnections != nil || ds.ServiceCategory != nil) &&
strings.HasPrefix(server.Type, tc.EdgeTypePrefix) {
fileName := "hdr_rw_" + *ds.XMLID + ".config"
if configFilesM, err = ensureConfigFile(configFilesM, fileName, configDir); err != nil {
warnings = append(warnings, "ensuring config file '"+fileName+"': "+err.Error())
}
}
} else if strings.HasPrefix(server.Type, tc.MidTypePrefix) {
if (ds.MidHeaderRewrite != nil || ds.MaxOriginConnections != nil || ds.ServiceCategory != nil) &&
ds.Type != nil && ds.Type.UsesMidCache() &&
strings.HasPrefix(server.Type, tc.MidTypePrefix) {
fileName := "hdr_rw_mid_" + *ds.XMLID + ".config"
if configFilesM, err = ensureConfigFile(configFilesM, fileName, configDir); err != nil {
warnings = append(warnings, "ensuring config file '"+fileName+"': "+err.Error())
}
}
}
if ds.RegexRemap != nil {
configFile := "regex_remap_" + *ds.XMLID + ".config"
if configFilesM, err = ensureConfigFile(configFilesM, configFile, configDir); err != nil {
warnings = append(warnings, "ensuring config file '"+configFile+"': "+err.Error())
}
}
if ds.SigningAlgorithm != nil && *ds.SigningAlgorithm == tc.SigningAlgorithmURLSig {
configFile := "url_sig_" + *ds.XMLID + ".config"
if configFilesM, err = ensureConfigFile(configFilesM, configFile, configDir); err != nil {
warnings = append(warnings, "ensuring config file '"+configFile+"': "+err.Error())
}
}
if ds.SigningAlgorithm != nil && *ds.SigningAlgorithm == tc.SigningAlgorithmURISigning {
configFile := "uri_signing_" + *ds.XMLID + ".config"
if configFilesM, err = ensureConfigFile(configFilesM, configFile, configDir); err != nil {
warnings = append(warnings, "ensuring config file '"+configFile+"': "+err.Error())
}
}
}
newFiles := []CfgMeta{}
for _, fis := range configFilesM {
for _, fi := range fis {
newFiles = append(newFiles, fi)
}
}
return newFiles, warnings, nil
}
// getURISignedDSes returns the URI-signed Delivery Services, and any warnings.
func getURISignedDSes(dses map[tc.DeliveryServiceName]DeliveryService) ([]tc.DeliveryServiceName, []string) {
warnings := []string{}
uriSignedDSes := []tc.DeliveryServiceName{}
for _, ds := range dses {
if ds.ID == nil {
warnings = append(warnings, "got delivery service with no id, skipping!")
continue
}
if ds.XMLID == nil {
warnings = append(warnings, "got delivery service with no xmlId (name), skipping!")
continue
}
if _, ok := dses[tc.DeliveryServiceName(*ds.XMLID)]; !ok {
continue // skip: this ds isn't assigned to this server, this is normal
}
if ds.SigningAlgorithm == nil || *ds.SigningAlgorithm != tc.SigningAlgorithmURISigning {
continue // not signed, so not in our list of signed dses to make config files for.
}
uriSignedDSes = append(uriSignedDSes, tc.DeliveryServiceName(*ds.XMLID))
}
return uriSignedDSes, warnings
}
// filterConfigFileDSes returns the DSes that should have config files for the given server.
// Returns the delivery services and any warnings.
func filterConfigFileDSes(server *Server, deliveryServices []DeliveryService, deliveryServiceServers []DeliveryServiceServer) (map[tc.DeliveryServiceName]DeliveryService, []string) {
warnings := []string{}
dses := map[tc.DeliveryServiceName]DeliveryService{}
if tc.CacheTypeFromString(server.Type) != tc.CacheTypeMid {
dsIDs := map[int]struct{}{}
for _, ds := range deliveryServices {
if ds.ID == nil {
warnings = append(warnings, "got delivery service with no ID, skipping!")
continue
}
dsIDs[*ds.ID] = struct{}{}
}
// TODO verify?
// serverIDs := []int{server.ID}
dssMap := map[int]struct{}{}
for _, dss := range deliveryServiceServers {
if dss.Server != *server.ID {
continue
}
if _, ok := dsIDs[dss.DeliveryService]; !ok {
continue
}
dssMap[dss.DeliveryService] = struct{}{}
}
for _, ds := range deliveryServices {
if ds.ID == nil {
warnings = append(warnings, "got deliveryservice with nil id, skipping!")
continue
}
if ds.XMLID == nil {
warnings = append(warnings, "got deliveryservice with nil xmlId (name), skipping!")
continue
}
if _, ok := dssMap[*ds.ID]; !ok && ds.Topology == nil {
continue
}
dses[tc.DeliveryServiceName(*ds.XMLID)] = ds
}
} else {
for _, ds := range deliveryServices {
if ds.ID == nil {
warnings = append(warnings, "got deliveryservice with nil id, skipping!")
continue
}
if ds.XMLID == nil {
warnings = append(warnings, "got deliveryservice with nil xmlId (name), skipping!")
continue
}
if ds.CDNID == nil || *ds.CDNID != *server.CDNID {
continue
}
dses[tc.DeliveryServiceName(*ds.XMLID)] = ds
}
}
return dses, warnings
}
// getTOURLAndReverseProxy returns the toURL and toReverseProxyURL if they exist, or empty strings if they don't.
func getTOURLAndReverseProxy(globalParams []tc.Parameter) (string, string) {
toReverseProxyURL := ""
toURL := ""
for _, param := range globalParams {
if param.Name == "tm.rev_proxy.url" {
toReverseProxyURL = param.Value
} else if param.Name == "tm.url" {
toURL = param.Value
}
if toReverseProxyURL != "" && toURL != "" {
break
}
}
return toURL, toReverseProxyURL
}
func getLocationParams(serverParams []tc.Parameter) map[string]configProfileParams {
locationParams := map[string]configProfileParams{}
for _, param := range serverParams {
if param.Name == "location" {
p := locationParams[param.ConfigFile]
p.Name = param.ConfigFile
p.Path = param.Value
locationParams[param.ConfigFile] = p
}
}
return locationParams
}
type configProfileParams struct {
Name string
Path string
}
func requiredFiles(atsMajorVer int) []string {
if atsMajorVer >= 9 {
return requiredFiles9()
}
return requiredFiles8()
}
// requiredFiles8 the list of config files required by ATS 8.
// Note these are not exhaustive. This is only used to error if these are missing.
// The presence of these is no guarantee the location Parameters are complete and correct.
func requiredFiles8() []string {
return []string{
"cache.config",
"hosting.config",
"ip_allow.config",
"parent.config",
"plugin.config",
"records.config",
"remap.config",
"ssl_server_name.yaml",
"storage.config",
"volume.config",
}
}
// requiredFiles9 is the list of config files required by ATS 9.
// Note these are not exhaustive. This is only used to error if these are missing.
// The presence of these is no guarantee the location Parameters are complete and correct.
func requiredFiles9() []string {
return []string{
"cache.config",
"hosting.config",
"ip_allow.yaml",
"parent.config",
"plugin.config",
"records.config",
"remap.config",
"sni.yaml",
"storage.config",
"volume.config",
"strategies.yaml",
}
}
// ensureConfigFile ensures files contains the given fileName. If so, returns files unmodified.
// If not, if configDir is empty, returns an error.
// If not, and configDir is nonempty, creates the given file, configDir location, and returns files.
func ensureConfigFile(files map[string][]CfgMeta, fileName string, configDir string) (map[string][]CfgMeta, error) {
if _, ok := files[fileName]; ok {
return files, nil
}
if configDir == "" {
return files, errors.New("required file '" + fileName + "' has no location Parameter, and ATS config directory not found.")
}
files[fileName] = []CfgMeta{{
Name: fileName,
Path: configDir,
}}
return files, nil
}