Skip to content

Commit

Permalink
Issue #16: Drive UI via API
Browse files Browse the repository at this point in the history
  • Loading branch information
magiconair committed Nov 28, 2015
1 parent d9cfc51 commit e05aa99
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 139 deletions.
6 changes: 3 additions & 3 deletions route/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import "strings"
var match matcher = prefixMatcher

// matcher determines whether a host/path matches a route
type matcher func(path string, r *route) bool
type matcher func(path string, r *Route) bool

// prefixMatcher matches path to the routes' path.
func prefixMatcher(path string, r *route) bool {
return strings.HasPrefix(path, r.path)
func prefixMatcher(path string, r *Route) bool {
return strings.HasPrefix(path, r.Path)
}
6 changes: 3 additions & 3 deletions route/picker.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
var pick picker = rndPicker

// Picker selects a target from a list of targets
type picker func(r *route) *Target
type picker func(r *Route) *Target

// SetPickerStrategy sets the picker function for the proxy.
func SetPickerStrategy(s string) error {
Expand All @@ -26,12 +26,12 @@ func SetPickerStrategy(s string) error {
}

// rndPicker picks a random target from the list of targets.
func rndPicker(r *route) *Target {
func rndPicker(r *Route) *Target {
return r.wTargets[randIntn(len(r.wTargets))]
}

// rrPicker picks the next target from a list of targets using round-robin.
func rrPicker(r *route) *Target {
func rrPicker(r *Route) *Target {
u := r.wTargets[r.total%uint64(len(r.wTargets))]
atomic.AddUint64(&r.total, 1)
return u
Expand Down
113 changes: 58 additions & 55 deletions route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ import (
// amount of traffic this route should get. You can specify
// that a route should get a fixed percentage of the traffic
// independent of how many instances are running.
type route struct {
// host contains the host of the route.
type Route struct {
// Host contains the host of the route.
// not used for routing but for config generation
// Table has a map with the host as key
// for faster lookup and smaller search space.
host string
Host string

// path is the path prefix from a request uri
path string
// Path is the path prefix from a request uri
Path string

// targets contains the list of URLs
targets []*Target
// Targets contains the list of URLs
Targets []*Target

// wTargets contains 100 targets distributed
// according to their weight and ordered RR in the
Expand All @@ -39,59 +39,59 @@ type route struct {
total uint64
}

func newRoute(host, path string) *route {
return &route{host: host, path: path}
func newRoute(host, path string) *Route {
return &Route{Host: host, Path: path}
}

func (r *route) addTarget(service string, targetURL *url.URL, fixedWeight float64, tags []string) {
func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float64, tags []string) {
if fixedWeight < 0 {
fixedWeight = 0
}

name := metrics.TargetName(service, r.host, r.path, targetURL)
name := metrics.TargetName(service, r.Host, r.Path, targetURL)
timer := gometrics.GetOrRegisterTimer(name, gometrics.DefaultRegistry)

t := &Target{service: service, tags: tags, URL: targetURL, fixedWeight: fixedWeight, Timer: timer}
r.targets = append(r.targets, t)
t := &Target{Service: service, Tags: tags, URL: targetURL, FixedWeight: fixedWeight, Timer: timer}
r.Targets = append(r.Targets, t)
r.weighTargets()
}

func (r *route) delService(service string) {
func (r *Route) delService(service string) {
var clone []*Target
for _, t := range r.targets {
if t.service == service {
for _, t := range r.Targets {
if t.Service == service {
continue
}
clone = append(clone, t)
}
r.targets = clone
r.Targets = clone
r.weighTargets()
}

func (r *route) delTarget(service string, targetURL *url.URL) {
func (r *Route) delTarget(service string, targetURL *url.URL) {
var clone []*Target
for _, t := range r.targets {
if t.service == service && t.URL.String() == targetURL.String() {
for _, t := range r.Targets {
if t.Service == service && t.URL.String() == targetURL.String() {
continue
}
clone = append(clone, t)
}
r.targets = clone
r.Targets = clone
r.weighTargets()
}

func (r *route) setWeight(service string, weight float64, tags []string) int {
func (r *Route) setWeight(service string, weight float64, tags []string) int {
loop := func(w float64) int {
n := 0
for _, t := range r.targets {
if service != "" && t.service != service {
for _, t := range r.Targets {
if service != "" && t.Service != service {
continue
}
if len(tags) > 0 && !contains(t.tags, tags) {
if len(tags) > 0 && !contains(t.Tags, tags) {
continue
}
n++
t.fixedWeight = w
t.FixedWeight = w
}
return n
}
Expand Down Expand Up @@ -128,7 +128,7 @@ func contains(src, dst []string) bool {
}

// targetWeight returns how often target is in wTargets.
func (r *route) targetWeight(targetURL string) (n int) {
func (r *Route) targetWeight(targetURL string) (n int) {
for _, t := range r.wTargets {
if t.URL.String() == targetURL {
n++
Expand All @@ -137,25 +137,28 @@ func (r *route) targetWeight(targetURL string) (n int) {
return n
}

func (r *Route) TargetConfig(t *Target, addWeight bool) string {
s := fmt.Sprintf("route add %s %s %s", t.Service, r.Host+r.Path, t.URL)
if addWeight {
s += fmt.Sprintf(" weight %2.2f", t.Weight)
} else if t.FixedWeight > 0 {
s += fmt.Sprintf(" weight %.2f", t.FixedWeight)
}
if len(t.Tags) > 0 {
s += fmt.Sprintf(" tags %q", strings.Join(t.Tags, ","))
}
return s
}

// config returns the route configuration in the config language.
// with the weights specified by the user.
func (r *route) config(addWeight bool) []string {
func (r *Route) config(addWeight bool) []string {
var cfg []string
for _, t := range r.targets {
if t.weight <= 0 {
for _, t := range r.Targets {
if t.Weight <= 0 {
continue
}

s := fmt.Sprintf("route add %s %s %s", t.service, r.host+r.path, t.URL)
if addWeight {
s += fmt.Sprintf(" weight %2.2f", t.weight)
} else if t.fixedWeight > 0 {
s += fmt.Sprintf(" weight %.2f", t.fixedWeight)
}
if len(t.tags) > 0 {
s += fmt.Sprintf(" tags %q", strings.Join(t.tags, ","))
}
cfg = append(cfg, s)
cfg = append(cfg, r.TargetConfig(t, addWeight))
}
return cfg
}
Expand All @@ -168,35 +171,35 @@ func (r *route) config(addWeight bool) []string {
//
// Targets with a dynamic weight will receive an equal share of the remaining
// traffic if there is any left.
func (r *route) weighTargets() {
func (r *Route) weighTargets() {
// how big is the fixed weighted traffic?
var nFixed int
var sumFixed float64
for _, t := range r.targets {
if t.fixedWeight > 0 {
for _, t := range r.Targets {
if t.FixedWeight > 0 {
nFixed++
sumFixed += t.fixedWeight
sumFixed += t.FixedWeight
}
}

// normalize fixed weights up (sumFixed < 1) or down (sumFixed > 1)
scale := 1.0
if sumFixed > 1 || (nFixed == len(r.targets) && sumFixed < 1) {
if sumFixed > 1 || (nFixed == len(r.Targets) && sumFixed < 1) {
scale = 1 / sumFixed
}

// compute the weight for the targets with dynamic weights
dynamic := (1 - sumFixed) / float64(len(r.targets)-nFixed)
dynamic := (1 - sumFixed) / float64(len(r.Targets)-nFixed)
if dynamic < 0 {
dynamic = 0
}

// assign the actual weight to each target
for _, t := range r.targets {
if t.fixedWeight > 0 {
t.weight = t.fixedWeight * scale
for _, t := range r.Targets {
if t.FixedWeight > 0 {
t.Weight = t.FixedWeight * scale
} else {
t.weight = dynamic
t.Weight = dynamic
}
}

Expand All @@ -215,10 +218,10 @@ func (r *route) weighTargets() {
// because of rounding errors
gotSlots, wantSlots := 0, 100

slotCount := make(byN, len(r.targets))
for i, t := range r.targets {
slotCount := make(byN, len(r.Targets))
for i, t := range r.Targets {
slotCount[i].i = i
slotCount[i].n = int(float64(wantSlots)*t.weight + 0.5)
slotCount[i].n = int(float64(wantSlots)*t.Weight + 0.5)
gotSlots += slotCount[i].n
}
sort.Sort(slotCount)
Expand All @@ -237,7 +240,7 @@ func (r *route) weighTargets() {
}

// use slot and move to next one
slots[next] = r.targets[c.i]
slots[next] = r.Targets[c.i]
next = (next + step) % gotSlots
}
}
Expand Down
2 changes: 1 addition & 1 deletion route/route_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func makeRequests(t Table) []*http.Request {
reqs := []*http.Request{}
for host, hr := range t {
for _, r := range hr {
req := &http.Request{Host: host, RequestURI: r.path + "/some/additional/path"}
req := &http.Request{Host: host, RequestURI: r.Path + "/some/additional/path"}
reqs = append(reqs, req)
}
}
Expand Down
6 changes: 3 additions & 3 deletions route/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func mustParse(rawurl string) *url.URL {

func TestNewRoute(t *testing.T) {
r := newRoute("www.bar.com", "/foo")
if got, want := r.path, "/foo"; got != want {
if got, want := r.Path, "/foo"; got != want {
t.Errorf("got %q want %q", got, want)
}
}
Expand All @@ -27,10 +27,10 @@ func TestAddTarget(t *testing.T) {
r := newRoute("www.bar.com", "/foo")
r.addTarget("service", u, 0, nil)

if got, want := len(r.targets), 1; got != want {
if got, want := len(r.Targets), 1; got != want {
t.Errorf("target length: got %d want %d", got, want)
}
if got, want := r.targets[0].URL, u; got != want {
if got, want := r.Targets[0].URL, u; got != want {
t.Errorf("target url: got %s want %s", got, want)
}
config := []string{"route add service www.bar.com/foo http://foo.com/"}
Expand Down
12 changes: 6 additions & 6 deletions route/routes.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package route

// routes stores a list of routes usually for a single host.
type routes []*route
type Routes []*Route

// find returns the route with the given path and returns nil if none was found.
func (rt routes) find(path string) *route {
func (rt Routes) find(path string) *Route {
for _, r := range rt {
if r.path == path {
if r.Path == path {
return r
}
}
return nil
}

// sort by path in reverse order (most to least specific)
func (rt routes) Len() int { return len(rt) }
func (rt routes) Swap(i, j int) { rt[i], rt[j] = rt[j], rt[i] }
func (rt routes) Less(i, j int) bool { return rt[j].path < rt[i].path }
func (rt Routes) Len() int { return len(rt) }
func (rt Routes) Swap(i, j int) { rt[i], rt[j] = rt[j], rt[i] }
func (rt Routes) Less(i, j int) bool { return rt[j].Path < rt[i].Path }
14 changes: 7 additions & 7 deletions route/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func SetTable(t Table) {
// Table contains a set of routes grouped by host.
// The host routes are sorted from most to least specific
// by sorting the routes in reverse order by path.
type Table map[string]routes
type Table map[string]Routes

// hostpath splits a host/path prefix into a host and a path.
// The path always starts with a slash
Expand Down Expand Up @@ -73,7 +73,7 @@ func (t Table) AddRoute(service, prefix, target string, weight float64, tags []s

// add new host
if t[host] == nil {
t[host] = routes{r}
t[host] = Routes{r}
return nil
}

Expand Down Expand Up @@ -147,7 +147,7 @@ func (t Table) DelRoute(service, prefix, target string) error {
}

// route finds the route for host/path or returns nil if none exists.
func (t Table) route(host, path string) *route {
func (t Table) route(host, path string) *Route {
hr := t[host]
if hr == nil {
return nil
Expand Down Expand Up @@ -187,20 +187,20 @@ func (t Table) doLookup(host, path, trace string) *Target {

for _, r := range hr {
if match(path, r) {
n := len(r.targets)
n := len(r.Targets)
if n == 0 {
return nil
}
if n == 1 {
return r.targets[0]
return r.Targets[0]
}
if trace != "" {
log.Printf("[TRACE] %s Match %s%s", trace, r.host, r.path)
log.Printf("[TRACE] %s Match %s%s", trace, r.Host, r.Path)
}
return pick(r)
}
if trace != "" {
log.Printf("[TRACE] %s No match %s%s", trace, r.host, r.path)
log.Printf("[TRACE] %s No match %s%s", trace, r.Host, r.Path)
}
}
return nil
Expand Down
Loading

0 comments on commit e05aa99

Please sign in to comment.