blob: 65aa8ec1b975fbfd6be0c45e9b848a495a29f189 [file] [log] [blame]
package topology
/*
* 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 (
"fmt"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
"strings"
)
func checkUniqueCacheGroupNames(nodes []tc.TopologyNode) error {
cacheGroupNames := map[string]bool{}
for _, node := range nodes {
if _, exists := cacheGroupNames[node.Cachegroup]; exists {
return fmt.Errorf("cachegroup %v cannot be used more than once in the topology", node.Cachegroup)
}
cacheGroupNames[node.Cachegroup] = true
}
return nil
}
func checkForDuplicateParents(nodes []tc.TopologyNode, index int) error {
parents := nodes[index].Parents
if len(parents) != 2 || parents[0] != parents[1] {
return nil
}
return fmt.Errorf("cachegroup %v cannot be both a primary and secondary parent of cachegroup %v", nodes[parents[0]].Cachegroup, nodes[index].Cachegroup)
}
func checkForSelfParents(nodes []tc.TopologyNode, index int) error {
for _, parentIndex := range nodes[index].Parents {
if index == parentIndex {
return fmt.Errorf("cachegroup %v cannot be a parent of itself", index)
}
}
return nil
}
// checkForEdgeParents returns an error if an index given in the parents array, adds a warning + returns a nil error if
// an edge parents an edge, and returns an error if an edge parents a non-edge cachegroup.
func (topology *TOTopology) checkForEdgeParents(cacheGroups []tc.CacheGroupNullable, nodeIndex int) error {
node := topology.Nodes[nodeIndex]
errs := make([]error, len(node.Parents))
for parentIndex, parentCacheGroupIndex := range node.Parents {
if parentCacheGroupIndex < 0 || parentCacheGroupIndex >= len(topology.Nodes) {
errs = append(errs, fmt.Errorf("parent %d of cachegroup %s refers to a cachegroup at index %d, but no such cachegroup exists", parentIndex, node.Cachegroup, parentCacheGroupIndex))
break
}
parentCacheGroupType := *cacheGroups[parentCacheGroupIndex].Type
if parentCacheGroupType != tc.CacheGroupEdgeTypeName {
continue
}
switch cacheGroupType := *cacheGroups[nodeIndex].Type; cacheGroupType {
case tc.CacheGroupEdgeTypeName:
parentTerm := "parent"
if parentIndex == 1 {
parentTerm = "secondary " + parentTerm
}
topology.Alerts.AddNewAlert(tc.WarnLevel, fmt.Sprintf(
"%s-typed cachegroup %s is a %s of %s, unexpected behavior may result",
parentCacheGroupType,
topology.Nodes[parentCacheGroupIndex].Cachegroup,
parentTerm,
node.Cachegroup))
default:
errs = append(errs, fmt.Errorf(
"cachegroup %s's type is %s; it cannot parent a %s-typed cachegroup %s",
topology.Nodes[parentCacheGroupIndex].Cachegroup,
parentCacheGroupType,
cacheGroupType,
node.Cachegroup))
}
}
return util.JoinErrs(errs)
}
func checkForLeafMids(nodes []tc.TopologyNode, cacheGroups []tc.CacheGroupNullable) []tc.TopologyNode {
isLeafMid := make([]bool, len(nodes))
for index := range isLeafMid {
isLeafMid[index] = true
}
for index, node := range nodes {
if *cacheGroups[index].Type == tc.CacheGroupEdgeTypeName {
isLeafMid[index] = false
}
for _, parentIndex := range node.Parents {
if !isLeafMid[parentIndex] {
continue
}
isLeafMid[parentIndex] = false
}
}
var leafMids []tc.TopologyNode
for index, node := range nodes {
if isLeafMid[index] {
leafMids = append(leafMids, node)
}
}
return leafMids
}
func checkForCycles(nodes []tc.TopologyNode) ([]string, error) {
components := tarjan(nodes)
var (
errs []error
cacheGroups []string
)
for _, component := range components {
if len(component) > 1 {
errString := "cycle detected between cachegroups "
var node tc.TopologyNode
for _, node = range component {
cacheGroups = append(cacheGroups, node.Cachegroup)
errString += node.Cachegroup + ", "
}
length := len(errString)
cachegroupNameLength := len(node.Cachegroup)
errString = errString[0:length-2-cachegroupNameLength-2] + " and " + errString[length-2-cachegroupNameLength:length-2]
errs = append(errs, fmt.Errorf(errString))
}
}
if len(errs) == 0 {
return nil, nil
}
errs = append([]error{fmt.Errorf("topology cannot have cycles")}, errs...)
return cacheGroups, util.JoinErrs(errs)
}
func (topology *TOTopology) checkForCyclesAcrossTopologies() error {
var (
nodes []tc.TopologyNode
topologiesByCacheGroup map[string][]string
cacheGroups []string
err error
)
if nodes, topologiesByCacheGroup, err = topology.nodesInOtherTopologies(); err != nil {
return err
}
if cacheGroups, err = checkForCycles(nodes); err == nil {
return nil
}
if cacheGroups == nil {
return fmt.Errorf("unable to check topology %s for cycles across all topologies", topology.Name)
}
var involvedTopologies []string
includedTopology := map[string]bool{}
for _, cacheGroup := range cacheGroups {
for _, topology := range topologiesByCacheGroup[cacheGroup] {
if _, alreadyIncluded := includedTopology[topology]; alreadyIncluded {
continue
}
involvedTopologies = append(involvedTopologies, topology)
includedTopology[topology] = true
}
}
return fmt.Errorf("cycles exist between topology %s and topologies [%s]: %v", topology.Name, strings.Join(involvedTopologies, ", "), err)
}