Feature: allow arbitrary order in proxy group (#89)

This commit is contained in:
Comzyh 2019-08-12 10:11:44 +08:00 committed by Dreamacro
parent b137a50d85
commit d59e98dc83
2 changed files with 129 additions and 1 deletions

View File

@ -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)
}

View File

@ -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)
}