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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
package discovery
import (
"fmt"
"math/rand"
"os"
"reflect"
"regexp"
"strings"
"time"
"github.com/mimecast/dtail/internal/io/dlog"
)
// ServerOrder to specify how to sort the server list.
type ServerOrder int
const (
// Shuffle the server list?
Shuffle ServerOrder = iota
)
// Discovery method for discovering a list of available DTail servers.
type Discovery struct {
// To plug in a custom server discovery module.
module string
// To specify optional server discovery module options.
options string
// To either filter a server list or to secify an exact list.
server string
// To filter server list.
regex *regexp.Regexp
// How to order the server list.
order ServerOrder
}
// New returns a new discovery method.
func New(method, server string, order ServerOrder) *Discovery {
module := method
options := ""
if strings.Contains(module, ":") {
s := strings.Split(module, ":")
if len(s) != 2 {
dlog.Common.FatalPanic("Unable to parse discovery module", module)
}
module = s[0]
options = s[1]
}
d := Discovery{
module: strings.ToUpper(module),
options: options,
server: server,
order: order,
}
if strings.HasPrefix(server, "/") && strings.HasSuffix(server, "/") {
d.initRegex()
}
return &d
}
func (d *Discovery) initRegex() {
var runes []rune
last := len(d.server) - 1
for i, char := range d.server {
if i != 0 && i != last {
runes = append(runes, char)
}
}
regexStr := string(runes)
dlog.Common.Debug("Using filter regex", regexStr)
regex, err := regexp.Compile(regexStr)
if err != nil {
dlog.Common.FatalPanic("Could not compile regex", regexStr, err)
}
d.regex = regex
d.server = ""
}
// ServerList to connect to via DTail client.
func (d *Discovery) ServerList() []string {
servers := d.serverListFromModule()
if d.regex != nil {
servers = d.filterList(servers)
}
servers = d.dedupList(servers)
if d.order == Shuffle {
servers = d.shuffleList(servers)
}
dlog.Common.Debug("Discovered servers", len(servers), servers)
return servers
}
func (d *Discovery) serverListFromModule() []string {
if d.module != "" {
return d.serverListFromReflectedModule()
}
if _, err := os.Stat(d.server); err == nil {
// Appears to be a file name, now try to read from that file.
return d.ServerListFromFILE()
}
// Appears to be a list of FQDNs (or a single FQDN)
return d.ServerListFromCOMMA()
}
// The aim of this is that everyone can plug in their own server discovery
// method to DTail. Just add a method ServerListFrommMODULENAME to type
// Discovery. Whereas MODULENAME must be a upeprcase string.
func (d *Discovery) serverListFromReflectedModule() []string {
methodName := fmt.Sprintf("ServerListFrom%s", d.module)
// Now we are reflecting the serve discovery function by it's name.
rt := reflect.TypeOf(d)
reflectedMethod, ok := rt.MethodByName(methodName)
if !ok {
dlog.Common.FatalPanic("No such server discovery module", d.module, methodName)
}
inputValues := make([]reflect.Value, 1)
// Thist input value is method receiver.
inputValues[0] = reflect.ValueOf(d)
returnValues := reflectedMethod.Func.Call(inputValues)
// First return value is server list.
return returnValues[0].Interface().([]string)
}
// Filter server list based on a regexp.
func (d *Discovery) filterList(servers []string) (filtered []string) {
dlog.Common.Debug("Filtering server list")
for _, server := range servers {
if d.regex.MatchString(server) {
filtered = append(filtered, server)
}
}
return
}
// Deduplicate the server list.
func (d *Discovery) dedupList(servers []string) (deduped []string) {
serverMap := make(map[string]struct{}, len(servers))
for _, server := range servers {
if _, ok := serverMap[server]; !ok {
serverMap[server] = struct{}{}
deduped = append(deduped, server)
}
}
dlog.Common.Debug("Deduped server list", len(servers), len(deduped))
return
}
// Randomly shuffle the server list.
func (d *Discovery) shuffleList(servers []string) []string {
dlog.Common.Debug("Shuffling server list")
r := rand.New(rand.NewSource(time.Now().Unix()))
shuffled := make([]string, len(servers))
n := len(servers)
for i := 0; i < n; i++ {
randIndex := r.Intn(len(servers))
shuffled[i] = servers[randIndex]
servers = append(servers[:randIndex], servers[randIndex+1:]...)
}
return shuffled
}
|