forked from uber/ringpop-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
util.go
259 lines (216 loc) · 6.93 KB
/
util.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
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package util
import (
"errors"
"fmt"
"math/rand"
"net"
"regexp"
"strconv"
"time"
)
// HostportPattern is regex to match a host:port
var HostportPattern = regexp.MustCompile(`^(\d+.\d+.\d+.\d+):\d+$`)
// CaptureHost takes a host:port and returns the host.
func CaptureHost(hostport string) string {
host, _, err := net.SplitHostPort(hostport)
if err != nil {
return ""
}
return host
}
// CheckHostnameIPMismatch checks for a hostname/IP mismatch in a map of hosts to
// host:ports. If there is a mismatch, returns the mismatched hosts and an error,
// otherwise nil and nil.
func CheckHostnameIPMismatch(local string, hostsMap map[string][]string) ([]string, error) {
var message string
test := func(message string, filter func(string) bool) ([]string, error) {
var mismatched []string
for _, hostports := range hostsMap {
for _, hostport := range hostports {
if filter(hostport) {
mismatched = append(mismatched, hostport)
}
}
}
if len(mismatched) > 0 {
return mismatched, errors.New(message)
}
return nil, nil
}
if HostportPattern.MatchString(local) {
message = "Your host identifier looks like an IP address and there are bootstrap " +
"hosts that appear to be specified with hostnames. These inconsistencies may " +
"lead to subtle node communication issues."
return test(message, func(hostport string) bool {
return !HostportPattern.MatchString(hostport)
})
}
message = "Your host identifier looks like a hostname and there are bootstrap hosts " +
"that appear to be specified with IP addresses. These inconsistencies may lead " +
"to subtle node communication issues"
return test(message, func(hostport string) bool {
return HostportPattern.MatchString(hostport)
})
}
// CheckLocalMissing checks a slice of host:ports for the given local host:port, return
// an error if not found, otherwise nil.
func CheckLocalMissing(local string, hostports []string) error {
if !StringInSlice(hostports, local) {
return errors.New("local node missing from hosts")
}
return nil
}
// SingleNodeCluster determines if hostport is the only host:port contained within
// the hostMap.
func SingleNodeCluster(hostport string, hostMap map[string][]string) bool {
// If the map contains more than one host then clearly there is more than
// one address so we can't be a single node cluster.
if len(hostMap) > 1 {
return false
}
host := CaptureHost(hostport)
// If hostport is not even in the map at all then it's not possible for
// us to be a single node cluster.
_, ok := hostMap[host]
if !ok {
return false
}
// Same as above; there is most than one host
if len(hostMap[host]) > 1 {
return false
}
// There is a single hostport in the map; check that it matches
if hostMap[host][0] == hostport {
return true
}
// There is a single hostport in the map but it doesn't match the given
// hostport.
return false
}
// HostPortsByHost parses a list of host/port conbinations and creates a map
// of unique hosts each containing a slice of the hostsport instances for each
// host.
func HostPortsByHost(hostports []string) (hostMap map[string][]string) {
hostMap = make(map[string][]string)
for _, hostport := range hostports {
host := CaptureHost(hostport)
if host != "" {
hostMap[host] = append(hostMap[host], hostport)
}
}
return
}
// TimeZero returns a time such that time.IsZero() is true
func TimeZero() time.Time {
return time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)
}
// TimeNowMS returns Unix time in milliseconds for time.Now()
func TimeNowMS() int64 {
return UnixMS(time.Now())
}
// MS returns the number of milliseconds given in a duration
func MS(d time.Duration) int64 {
return d.Nanoseconds() / 1000000
}
// UnixMS returns Unix time in milliseconds for the given time
func UnixMS(t time.Time) int64 {
return t.UnixNano() / 1000000
}
// StringInSlice returns whether the string is contained within the slice.
func StringInSlice(slice []string, str string) bool {
for _, b := range slice {
if str == b {
return true
}
}
return false
}
// ShuffleStrings takes a slice of strings and returns a new slice containing
// the same strings in a random order.
func ShuffleStrings(strings []string) []string {
newStrings := make([]string, len(strings))
newIndexes := rand.Perm(len(strings))
for o, n := range newIndexes {
newStrings[n] = strings[o]
}
return newStrings
}
// TakeNode takes an element from nodes at the given index, or at a random index if
// index < 0. Mutates nodes.
func TakeNode(nodes *[]string, index int) string {
if len(*nodes) == 0 {
return ""
}
var i int
if index >= 0 {
if index >= len(*nodes) {
return ""
}
i = index
} else {
i = rand.Intn(len(*nodes))
}
node := (*nodes)[i]
*nodes = append((*nodes)[:i], (*nodes)[i+1:]...)
return node
}
// SelectInt takes an option and a default value and returns the default value if
// the option is equal to zero, and the option otherwise.
func SelectInt(opt, def int) int {
if opt == 0 {
return def
}
return opt
}
// SelectDuration takes an option and a default value and returns the default value if
// the option is equal to zero, and the option otherwise.
func SelectDuration(opt, def time.Duration) time.Duration {
if opt == time.Duration(0) {
return def
}
return opt
}
// Min returns min(a,b)
func Min(a, b int) int {
if a < b {
return a
}
return b
}
// Timestamp is a bi-directional binding of Time to interger Unix timestamp in
// JSON.
type Timestamp time.Time
// MarshalJSON returns the JSON encoding of the timestamp.
func (t *Timestamp) MarshalJSON() ([]byte, error) {
ts := time.Time(*t).Unix()
stamp := fmt.Sprint(ts)
return []byte(stamp), nil
}
// UnmarshalJSON sets the timestamp to the value in the specified JSON.
func (t *Timestamp) UnmarshalJSON(b []byte) error {
ts, err := strconv.Atoi(string(b))
if err != nil {
return err
}
*t = Timestamp(time.Unix(int64(ts), 0))
return nil
}