-
Notifications
You must be signed in to change notification settings - Fork 621
/
service.go
136 lines (115 loc) · 3.7 KB
/
service.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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package consul
import (
"log"
"net"
"runtime"
"sort"
"strconv"
"strings"
"time"
"github.com/hashicorp/consul/api"
)
// watchServices monitors the consul health checks and creates a new configuration
// on every change.
func watchServices(client *api.Client, tagPrefix string, status []string, config chan string) {
var lastIndex uint64
for {
q := &api.QueryOptions{RequireConsistent: true, WaitIndex: lastIndex}
checks, meta, err := client.Health().State("any", q)
if err != nil {
log.Printf("[WARN] consul: Error fetching health state. %v", err)
time.Sleep(time.Second)
continue
}
log.Printf("[INFO] consul: Health changed to #%d", meta.LastIndex)
config <- servicesConfig(client, passingServices(checks, status), tagPrefix)
lastIndex = meta.LastIndex
}
}
// servicesConfig determines which service instances have passing health checks
// and then finds the ones which have tags with the right prefix to build the config from.
func servicesConfig(client *api.Client, checks []*api.HealthCheck, tagPrefix string) string {
// map service name to list of service passing for which the health check is ok
m := map[string]map[string]bool{}
for _, check := range checks {
name, id := check.ServiceName, check.ServiceID
if _, ok := m[name]; !ok {
m[name] = map[string]bool{}
}
m[name][id] = true
}
var config []string
for name, passing := range m {
cfg := serviceConfig(client, name, passing, tagPrefix)
config = append(config, cfg...)
}
// sort config in reverse order to sort most specific config to the top
sort.Sort(sort.Reverse(sort.StringSlice(config)))
return strings.Join(config, "\n")
}
// serviceConfig constructs the config for all good instances of a single service.
func serviceConfig(client *api.Client, name string, passing map[string]bool, tagPrefix string) (config []string) {
if name == "" || len(passing) == 0 {
return nil
}
dc, err := datacenter(client)
if err != nil {
log.Printf("[WARN] consul: Error getting datacenter. %s", err)
return nil
}
q := &api.QueryOptions{RequireConsistent: true}
svcs, _, err := client.Catalog().Service(name, "", q)
if err != nil {
log.Printf("[WARN] consul: Error getting catalog service %s. %v", name, err)
return nil
}
env := map[string]string{
"DC": dc,
}
for _, svc := range svcs {
// check if the instance is in the list of instances
// which passed the health check
if _, ok := passing[svc.ServiceID]; !ok {
continue
}
// get all tags which do not have the tag prefix
var svctags []string
for _, tag := range svc.ServiceTags {
if !strings.HasPrefix(tag, tagPrefix) {
svctags = append(svctags, tag)
}
}
// generate route commands
for _, tag := range svc.ServiceTags {
if route, opts, ok := parseURLPrefixTag(tag, tagPrefix, env); ok {
name, addr, port := svc.ServiceName, svc.ServiceAddress, svc.ServicePort
// use consul node address if service address is not set
if addr == "" {
addr = svc.Address
}
// add .local suffix on OSX for simple host names w/o domain
if runtime.GOOS == "darwin" && !strings.Contains(addr, ".") && !strings.HasSuffix(addr, ".local") {
addr += ".local"
}
// build route command
addr = net.JoinHostPort(addr, strconv.Itoa(port))
dst := "http://" + addr + "/"
if strings.Contains(opts, "proto=tcp") {
dst = "tcp://" + addr
} else if strings.Contains(opts, "proto=https") {
dst = "https://" + addr
}
tags := strings.Join(svctags, ",")
cfg := "route add " + name + " " + route + " " + dst
if tags != "" {
cfg += " tags " + strconv.Quote(tags)
}
if opts != "" {
cfg += " opts " + strconv.Quote(opts)
}
config = append(config, cfg)
}
}
}
return config
}