options.go (222 lines of code) (raw):

// 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/uber/ringpop-go/hashring" "github.com/uber/ringpop-go/logging" "github.com/uber/ringpop-go/membership" "github.com/uber/ringpop-go/shared" "github.com/uber/ringpop-go/swim" "github.com/uber/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 } } // NodeOverride is used to optionally override the default SWIM-based // gossip-style node membership protocol used for membership management. func NodeOverride(node swim.NodeInterface) Option { return func(r *Ringpop) error { r.node = node 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, }