forked from zalando/skipper
-
Notifications
You must be signed in to change notification settings - Fork 0
/
balance.go
95 lines (72 loc) · 2.72 KB
/
balance.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package loadbalancer
import (
"fmt"
"github.com/zalando/skipper/eskip"
)
func createGroupName(routeID string) string {
return fmt.Sprintf("__lb_group_%s", routeID)
}
func createMemberID(routeID string, index int) string {
return fmt.Sprintf("__lb_route_%s_%d", routeID, index)
}
func createDecisionRoute(original *eskip.Route, groupName string, groupSize int) *eskip.Route {
dr := *original
// we keep the original ID, as this is the entry point for this set of routes
// we keep the original predicates, too, to avoid conflicts with other routing:
dr.Predicates = append(dr.Predicates, &eskip.Predicate{
Name: GroupPredicateName,
Args: []interface{}{groupName},
})
// original filters only in the member routes:
dr.Filters = []*eskip.Filter{{
Name: DecideFilterName,
Args: []interface{}{groupName, groupSize},
}}
dr.Shunt = false
dr.Backend = ""
dr.BackendType = eskip.LoopBackend
return &dr
}
func createMember(original *eskip.Route, groupName string, index int, backend string) *eskip.Route {
m := *original
m.Id = createMemberID(original.Id, index)
// we keep the original predicates, too, to avoid conflicts with other routing:
m.Predicates = append(m.Predicates, &eskip.Predicate{
Name: MemberPredicateName,
Args: []interface{}{groupName, index},
})
m.Shunt = false
m.BackendType = eskip.NetworkBackend
m.Backend = backend
// we keep the original filters to let them do their job
return &m
}
func createMembers(original *eskip.Route, groupName string, backends []string) []*eskip.Route {
var members []*eskip.Route
for i := range backends {
members = append(members, createMember(original, groupName, i, backends[i]))
}
return members
}
// TODO:
// - the default function should not load balance if there's only a single route
// - for special use cases on the side of the user code, provide an additional function that load balances even
// when there's only a single route (?)
// BalanceRoute automatically converts a single route to a set of routes load
// balanced between the provided backend addresses. It takes a route and a set
// of backend addresses, and returns a set of routes with each backend replaced
// by one of the provided ones, plus it generates a decision route. It
// automatically applies the load balancer predicate and the decision filter,
// and preserves all the other predicates and filters.
func BalanceRoute(r *eskip.Route, backends []string) []*eskip.Route {
if len(backends) == 0 {
return nil
}
var routes []*eskip.Route
groupName := createGroupName(r.Id)
decisionRoute := createDecisionRoute(r, groupName, len(backends))
routes = append(routes, decisionRoute)
memberRoutes := createMembers(r, groupName, backends)
routes = append(routes, memberRoutes...)
return routes
}