diff --git a/config/config.go b/config/config.go index 296c9761..942ee876 100644 --- a/config/config.go +++ b/config/config.go @@ -293,10 +293,13 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { } // parse proxy group + if err := proxyGroupsDagSort(groupsConfig); err != nil { + return nil, err + } for idx, mapping := range groupsConfig { groupType, existType := mapping["type"].(string) groupName, existName := mapping["name"].(string) - if !existType && existName { + if !(existType && existName) { return nil, fmt.Errorf("ProxyGroup %d: missing type or name", idx) } diff --git a/config/utils.go b/config/utils.go index 54e205f8..7c6b69f3 100644 --- a/config/utils.go +++ b/config/utils.go @@ -34,3 +34,128 @@ func or(pointers ...*int) *int { } return pointers[len(pointers)-1] } + +// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order. +// Meanwhile, record the original index in the config file. +// If loop is detected, return an error with location of loop. +func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { + + type Node struct { + indegree int + // topological order + topo int + // the origional data in `groupsConfig` + data map[string]interface{} + // `outdegree` and `from` are used in loop locating + outdegree int + from []string + } + + graph := make(map[string]*Node) + + // Step 1.1 build dependency graph + for idx, mapping := range groupsConfig { + // record original order in config file. + // this field can be used determinate the display order in FrontEnd. + mapping["configIdx"] = idx + groupName, existName := mapping["name"].(string) + if !existName { + return fmt.Errorf("ProxyGroup %d: missing name", idx) + } + if node, ok := graph[groupName]; ok { + if node.data != nil { + return fmt.Errorf("ProxyGroup %s: duplicate group name", groupName) + } + node.data = mapping + } else { + graph[groupName] = &Node{0, -1, mapping, 0, nil} + } + proxies, existProxies := mapping["proxies"] + if !existProxies { + return fmt.Errorf("ProxyGroup %s: the `proxies` field is requried", groupName) + } + for _, proxy := range proxies.([]interface{}) { + proxy := proxy.(string) + if node, ex := graph[proxy]; ex { + node.indegree++ + } else { + graph[proxy] = &Node{1, -1, nil, 0, nil} + } + } + } + // Step 1.2 Topological Sort + // topological index of **ProxyGroup** + index := 0 + queue := make([]string, 0) + for name, node := range graph { + // in the begning, put nodes that have `node.indegree == 0` into queue. + if node.indegree == 0 { + queue = append(queue, name) + } + } + // every element in queue have indegree == 0 + for ; len(queue) > 0; queue = queue[1:] { + name := queue[0] + node := graph[name] + if node.data != nil { + index++ + groupsConfig[len(groupsConfig)-index] = node.data + for _, proxy := range node.data["proxies"].([]interface{}) { + child := graph[proxy.(string)] + child.indegree-- + if child.indegree == 0 { + queue = append(queue, proxy.(string)) + } + } + } + delete(graph, name) + } + + // no loop is detected, return sorted ProxyGroup + if len(graph) == 0 { + return nil + } + + // if loop is detected, locate the loop and throw an error + // Step 2.1 rebuild the graph, fill `outdegree` and `from` filed + for name, node := range graph { + if node.data == nil { + continue + } + for _, proxy := range node.data["proxies"].([]interface{}) { + node.outdegree++ + child := graph[proxy.(string)] + if child.from == nil { + child.from = make([]string, 0, child.indegree) + } + child.from = append(child.from, name) + } + } + // Step 2.2 remove nodes outside the loop. so that we have only the loops remain in `graph` + queue = make([]string, 0) + // initialize queue with node have outdegree == 0 + for name, node := range graph { + if node.outdegree == 0 { + queue = append(queue, name) + } + } + // every element in queue have outdegree == 0 + for ; len(queue) > 0; queue = queue[1:] { + name := queue[0] + node := graph[name] + for _, f := range node.from { + graph[f].outdegree-- + if graph[f].outdegree == 0 { + queue = append(queue, f) + } + } + delete(graph, name) + } + // Step 2.3 report the elements in loop + loopElements := make([]string, 0, len(graph)) + for name := range graph { + loopElements = append(loopElements, name) + delete(graph, name) + } + return fmt.Errorf("Loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements) +}