This repository has been archived by the owner on May 28, 2023. It is now read-only.
forked from temporalio/ringpop-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
options.go
427 lines (381 loc) · 14.1 KB
/
options.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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
// 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 ringpop
import (
"errors"
"time"
"github.com/benbjohnson/clock"
log "github.com/uber-common/bark"
"github.com/temporalio/ringpop-go/hashring"
"github.com/temporalio/ringpop-go/logging"
"github.com/temporalio/ringpop-go/membership"
"github.com/temporalio/ringpop-go/shared"
"github.com/temporalio/ringpop-go/swim"
"github.com/temporalio/ringpop-go/util"
)
type configuration struct {
// App is the name used to uniquely identify members of the same ring.
// Members will only talk to other members with the same app name. Note
// that App is taken as an argument of the Ringpop constructor and not a
// configuration option. This is to prevent accidental misconfiguration.
App string
// Configure the period by which ringpop emits the stats
// "membership.checksum-periodic" and "ring.checksum-periodic".
// See funcs {Membership,Ring}ChecksumStatPeriod for specifics.
MembershipChecksumStatPeriod time.Duration
RingChecksumStatPeriod time.Duration
// StateTimeouts keeps the state transition timeouts for swim to use
StateTimeouts swim.StateTimeouts
// LabelLimits keeps track of configured limits on labels. Among things the
// number of labels and the size of key and value can be configured.
LabelLimits swim.LabelOptions
// InitialLabels configures the initial labels.
InitialLabels swim.LabelMap
// SelfEvict holds the settings with regards to self eviction
SelfEvict swim.SelfEvictOptions
// RequiresAppInPing configures if ringpop node should reject pings
// that don't contain app name
RequiresAppInPing bool
}
// An Option is a modifier functions that configure/modify a real Ringpop
// object.
//
// There are typically two types of runtime options you can provide: flags
// (functions that modify the object) and value options (functions the accept
// user-specific arguments and then return a function that modifies the
// object).
//
// For more information, see:
// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
//
type Option func(*Ringpop) error
// applyOptions applies runtime configuration options to the specified Ringpop
// instance.
func applyOptions(r *Ringpop, opts []Option) error {
for _, option := range opts {
err := option(r)
if err != nil {
return err
}
}
return nil
}
// checkOptions checks that the Ringpop instance has been properly configured
// with all the required options.
func checkOptions(rp *Ringpop) []error {
errs := []error{}
if rp.channel == nil {
errs = append(errs, errors.New("channel is required"))
}
if rp.addressResolver == nil {
errs = append(errs, errors.New("address resolver is nil"))
}
return errs
}
// Runtime options
// Clock is used to set the Clock mechanism. Testing harnesses will typically
// replace this with a mocked clock.
func Clock(c clock.Clock) Option {
return func(r *Ringpop) error {
if c == nil {
return errors.New("clock is required")
}
r.clock = c
return nil
}
}
// Channel is used to provide a TChannel instance that Ringpop should use for
// all communication.
//
// Example:
//
// rp, err := ringpop.New("my-app", ringpop.Channel(myChannel))
//
// Channel is a required option. The constructor will throw an error if this
// option is not present.
func Channel(ch shared.TChannel) Option {
return func(r *Ringpop) error {
r.channel = ch
return nil
}
}
// HashRingConfig takes a `HashRingConfiguration` struct that can be used to
// configure the hash ring.
//
// Example:
//
// rp, err := ringpop.New("my-app",
// ringpop.Channel(myChannel),
// ringpop.HashRingConfig(&HashRingConfiguration{
// ReplicaPoints: 100,
// }),
// )
//
// See documentation on the `HashRingConfiguration` struct for more information
// about what options are available.
func HashRingConfig(c *hashring.Configuration) Option {
return func(r *Ringpop) error {
r.configHashRing = c
return nil
}
}
// Logger is used to specify a bark-compatible logger that will be used for
// all Ringpop logging. If a logger is not provided, one will be created
// automatically.
func Logger(l log.Logger) Option {
return func(r *Ringpop) error {
logging.SetLogger(l)
return nil
}
}
// LogLevels is used to set the severity log level for all Ringpop named
// loggers.
func LogLevels(levels map[string]logging.Level) Option {
return func(r *Ringpop) error {
return logging.SetLevels(levels)
}
}
// Statter is used to specify a bark-compatible (bark.StatsReporter) stats
// reporter that will be used to record ringpop stats. If a statter is not
// provided, stats will be emitted to a null stats-reporter.
func Statter(s log.StatsReporter) Option {
return func(r *Ringpop) error {
r.statter = s
return nil
}
}
// Identity can be used to specify a custom string as the unique identifier for
// this node. The identity should be unique amongst other Ringpop instances; it
// is used in the hashring.
//
// By default, the hostport/address of the node is used as the identity in the
// hashring. An error is thrown if a hostport is manually specified using this
// option, as this would lead to unexpected behaviour. If you want to override
// the node's listening address, use the `Address` option.
func Identity(identity string) Option {
return func(r *Ringpop) error {
if util.HostportPattern.MatchString(identity) {
return ErrInvalidIdentity
}
r.config.InitialLabels[membership.IdentityLabelKey] = identity
return nil
}
}
// Address is used to specify a static hostport string as this Ringpop
// instance's address.
//
// Example:
//
// ringpop.New("my-app",
// ringpop.Channel(myChannel),
// ringpop.Address("10.32.12.2:21130"),
// )
//
// You should make sure the address matches the listening address of the
// TChannel object.
//
// By default, you do not need to provide an address. If you do not provide
// one, the address will be resolved automatically by the default resolver.
func Address(address string) Option {
return AddressResolverFunc(func() (string, error) {
return address, nil
})
}
// AddressResolver is a function that returns the listen interface/port
// that Ringpop should use as its address.
type AddressResolver func() (string, error)
// AddressResolverFunc is used to specify a function that will be called when
// the Ringpop instance needs to resolve its address (typically, on
// bootstrap).
func AddressResolverFunc(resolver AddressResolver) Option {
return func(r *Ringpop) error {
r.addressResolver = resolver
return nil
}
}
// StatPeriodNever defines a "period" which disables a periodic stat emission.
const StatPeriodNever = time.Duration(-1)
// StatPeriodDefault defines the default emission period for a periodic stat.
const StatPeriodDefault = time.Duration(5 * time.Second)
// MembershipChecksumStatPeriod configures the period between emissions of the
// stat 'membership.checksum-periodic'. Using a value <=0 (or StatPeriodNever)
// will disable emission of this stat. Using a value in (0, 10ms) will return
// an error, as that value is unrealistically small. Normal values must
// therefore be >=10ms. StatPeriodDefault defines the default.
func MembershipChecksumStatPeriod(period time.Duration) Option {
return func(r *Ringpop) error {
if period <= 0 {
period = StatPeriodNever
} else if period < 10*time.Millisecond {
return errors.New("membership checksum stat period invalid below 10 ms")
}
r.config.MembershipChecksumStatPeriod = period
return nil
}
}
// RingChecksumStatPeriod configures the period between emissions of the stat
// 'ring.checksum-periodic'. Using a value <=0 (or StatPeriodNever) will
// disable emission of this stat. Using a value in (0, 10ms) will return an
// error, as that value is unrealistically small. Normal values must therefore
// be >=10ms. StatPeriodDefault defines the default.
func RingChecksumStatPeriod(period time.Duration) Option {
return func(r *Ringpop) error {
if period <= 0 {
period = StatPeriodNever
} else if period < 10*time.Millisecond {
return errors.New("ring checksum stat period invalid below 10 ms")
}
r.config.RingChecksumStatPeriod = period
return nil
}
}
// SuspectPeriod configures the period it takes ringpop to declare a node faulty
// after ringpop has first detected the node to be unresponsive to a healthcheck.
// When a node is declared faulty it is removed from the consistent hashring and
// stops forwarding traffic to that node. All keys previously routed to that node
// will then be routed to the new owner of the key
func SuspectPeriod(period time.Duration) Option {
return func(r *Ringpop) error {
r.config.StateTimeouts.Suspect = period
return nil
}
}
// FaultyPeriod configures the period Ringpop keeps a faulty node in its memberlist.
// Even though the node will not receive any traffic it is still present in the
// list in case it will come back online later. After this timeout ringpop will
// remove the node from its membership list permanently. If a node happens to come
// back after it has been removed from the membership Ringpop still allows it to
// join and take its old position in the hashring. To remove the node from the
// distributed membership it will mark it as a tombstone which can be removed from
// every members membership list independently.
func FaultyPeriod(period time.Duration) Option {
return func(r *Ringpop) error {
r.config.StateTimeouts.Faulty = period
return nil
}
}
// TombstonePeriod configures the period of the last time of the lifecycle in of
// a node in the membership list. This period should give the gossip protocol the
// time it needs to disseminate this change. If configured too short the node in
// question might show up again in faulty state in the distributed memberlist of
// Ringpop.
func TombstonePeriod(period time.Duration) Option {
return func(r *Ringpop) error {
r.config.StateTimeouts.Tombstone = period
return nil
}
}
// LabelLimitCount limits the number of labels an application can set on this
// node.
func LabelLimitCount(count int) Option {
return func(r *Ringpop) error {
r.config.LabelLimits.Count = count
return nil
}
}
// LabelLimitKeySize limits the size that a key of a label can be.
func LabelLimitKeySize(size int) Option {
return func(r *Ringpop) error {
r.config.LabelLimits.KeySize = size
return nil
}
}
// LabelLimitValueSize limits the size that a value of a label can be.
func LabelLimitValueSize(size int) Option {
return func(r *Ringpop) error {
r.config.LabelLimits.ValueSize = size
return nil
}
}
// SelfEvictPingRatio configures the maximum percentage/ratio of the members to
// actively ping while self evicting. A bigger ratio would allow for bigger
// batch sizes during restarts without the self eviction being lost due to all
// nodes having the knowledge being shutdown at the same time.
//
// A smaller ratio will cause less network traffic and therefore slightly faster
// shutdown times. A ratio that exceeds 1 will be capped to one when the self
// eviction is executed as it does not make sense to send the gossip to the same
// node twice. A negative value will cause no pings to be sent out during self
// eviction.
//
// In no case will there be more pings sent then makes sense by the limit of the
// current piggyback count
func SelfEvictPingRatio(ratio float64) Option {
return func(r *Ringpop) error {
r.config.SelfEvict.PingRatio = ratio
return nil
}
}
// RequiresAppInPing configures if ringpop node should reject pings
// that don't contain app name
func RequiresAppInPing(requiresAppInPing bool) Option {
return func(r *Ringpop) error {
r.config.RequiresAppInPing = requiresAppInPing
return nil
}
}
// Default options
// defaultClock sets the ringpop clock interface to use the system clock
func defaultClock(r *Ringpop) error {
return Clock(clock.New())(r)
}
// defaultAddressResolver sets the default addressResolver
func defaultAddressResolver(r *Ringpop) error {
r.addressResolver = r.channelAddressResolver
return nil
}
// defaultLogLevels is the default configuration for all Ringpop named loggers.
func defaultLogLevels(r *Ringpop) error {
return LogLevels(map[string]logging.Level{
"damping": logging.Error,
"dissemination": logging.Error,
"gossip": logging.Error,
"join": logging.Warn,
"membership": logging.Error,
"ring": logging.Error,
"suspicion": logging.Error,
})(r)
}
func defaultStatter(r *Ringpop) error {
return Statter(noopStatsReporter{})(r)
}
func defaultHashRingOptions(r *Ringpop) error {
return HashRingConfig(defaultHashRingConfiguration)(r)
}
func defaultMembershipChecksumStatPeriod(r *Ringpop) error {
return MembershipChecksumStatPeriod(StatPeriodDefault)(r)
}
func defaultRingChecksumStatPeriod(r *Ringpop) error {
return RingChecksumStatPeriod(StatPeriodDefault)(r)
}
// defaultOptions are the default options/values when Ringpop is created. They
// can be overridden at runtime.
var defaultOptions = []Option{
defaultClock,
defaultAddressResolver,
defaultLogLevels,
defaultStatter,
defaultMembershipChecksumStatPeriod,
defaultRingChecksumStatPeriod,
defaultHashRingOptions,
}
var defaultHashRingConfiguration = &hashring.Configuration{
ReplicaPoints: 100,
}