Skip to content

Commit

Permalink
Merge branch 'release-v0.4.0'
Browse files Browse the repository at this point in the history
Closes #127
  • Loading branch information
dansimau committed Mar 14, 2016
2 parents 1825917 + 24fb6c2 commit 7fbf9f5
Show file tree
Hide file tree
Showing 13 changed files with 469 additions and 49 deletions.
50 changes: 37 additions & 13 deletions CHANGES.md
@@ -1,27 +1,50 @@
ringpop-go changes
==================

v0.DEV (to be released)
-----------------------
v0.4.0
------

* Remove `File` and `Host`-based discover providers in favor of
`DiscoverProvider` interface #119
* Ringpop will always assume the current host (`ringpop.node.identity`) is part
of the cluster. Previously, it was true for only `Host`-based discovery.
* Add Go 1.6 testing on CI
* Feature: Faulty nodes are now automatically reaped from nodes' membership
lists after (24 hours by default). #123
* New options for controlling suspect and reaping times. #123
* Add new `Ready` and `Destroyed` events to ringpop. #125
* Add additional logging to bring ringpop-go on par with ringpop-node log
messages. #116
* Fix bug where Ringpop automatically added itself to the bootstrap hosts for
host-based bootstrapping, but not other bootstrapping methods. #120
* Fix race condition where membership and hashring could be inconsistent with
each other. #112
* Remove `File` and `Host` options from bootstrap options in favor of
`DiscoverProvider` interface. #120
* Add Go 1.6 to testing on CI

### Release notes

`BootstrapOptions.File` and `BootstrapOptions.Hosts` are replaced with
#### Version incompatibility in protocol

Since 0.4.0 introduces a new node/member state, 0.4.0 is not backwards-compatible with previous versions.

Note rolling upgrades with older versions do work, but undefined behaviour will occur if two versions run in parallel for longer than the `FaultyPeriod` (default 24 hours).

#### Changes to Bootstrap

This release contains a breaking change to the options provided to the
`ringpop.Bootstrap` call.

`BootstrapOptions.File` and `BootstrapOptions.Hosts` have been replaced with
`BootstrapOptions.DiscoverProvider`. `DiscoverProvider` is an interface which
requires a single method:

type DiscoverProvider interface {
Hosts() ([]string, error)
}
```go
type DiscoverProvider interface {
Hosts() ([]string, error)
}
```

Ringpop comes with DiscoverProviders for the previous `File` and `Hosts`
options out of the box.

We have implemented compatible DiscoverProviders for both `File` and `Hosts`,
so you can now do for a JSON `File`:
To upgrade if you were previously using `File`:

```diff
+ "github.com/uber/ringpop-go/discovery/jsonfile"
Expand All @@ -39,6 +62,7 @@ For static `Hosts`:
+ bootstrapOpts.DiscoverProvider = statichosts.New("127.0.0.1:3000", "127.0.0.1:3001")
```


v0.3.0
------

Expand Down
6 changes: 6 additions & 0 deletions events/events.go
Expand Up @@ -49,3 +49,9 @@ type LookupEvent struct {
Key string
Duration time.Duration
}

// Ready is fired when ringpop has successfully bootstrapped and is ready to receive requests and other method calls.
type Ready struct{}

// Destroyed is fired when ringpop has been destroyed and should not be responding to requests or lookup requests.
type Destroyed struct{}
43 changes: 43 additions & 0 deletions options.go
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/uber/ringpop-go/hashring"
"github.com/uber/ringpop-go/logging"
"github.com/uber/ringpop-go/shared"
"github.com/uber/ringpop-go/swim"
)

type configuration struct {
Expand All @@ -43,6 +44,9 @@ type configuration struct {
// 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
}

// An Option is a modifier functions that configure/modify a real Ringpop
Expand Down Expand Up @@ -237,6 +241,45 @@ func RingChecksumStatPeriod(period time.Duration) Option {
}
}

// 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
}
}

// Default options

// defaultClock sets the ringpop clock interface to use the system clock
Expand Down
47 changes: 47 additions & 0 deletions options_test.go
Expand Up @@ -225,6 +225,53 @@ func (s *RingpopOptionsTestSuite) TestTooSmallRingChecksumStatPeriod() {
s.Error(err)
}

func (s *RingpopOptionsTestSuite) TestSuspectPeriodConfig() {
rp, err := New("test", Channel(s.channel), SuspectPeriod(1*time.Second))
s.Require().NoError(err)
s.Require().NotNil(rp)

s.Equal(rp.config.StateTimeouts.Suspect, 1*time.Second)
s.Equal(rp.config.StateTimeouts.Faulty, time.Duration(0))
s.Equal(rp.config.StateTimeouts.Tombstone, time.Duration(0))
}

func (s *RingpopOptionsTestSuite) TestFaultyPeriodConfig() {
rp, err := New("test", Channel(s.channel), FaultyPeriod(2*time.Second))
s.Require().NoError(err)
s.Require().NotNil(rp)

s.Equal(rp.config.StateTimeouts.Suspect, time.Duration(0))
s.Equal(rp.config.StateTimeouts.Faulty, 2*time.Second)
s.Equal(rp.config.StateTimeouts.Tombstone, time.Duration(0))
}

func (s *RingpopOptionsTestSuite) TestTombstonePeriodConfig() {
rp, err := New("test", Channel(s.channel), TombstonePeriod(3*time.Second))
s.Require().NoError(err)
s.Require().NotNil(rp)

s.Equal(rp.config.StateTimeouts.Suspect, time.Duration(0))
s.Equal(rp.config.StateTimeouts.Faulty, time.Duration(0))
s.Equal(rp.config.StateTimeouts.Tombstone, 3*time.Second)
}

func (s *RingpopOptionsTestSuite) TestCombinedPeriodConfig() {
rp, err := New(
"test",
Channel(s.channel),
SuspectPeriod(1*time.Second),
FaultyPeriod(2*time.Second),
TombstonePeriod(3*time.Second),
)

s.Require().NoError(err)
s.Require().NotNil(rp)

s.Equal(rp.config.StateTimeouts.Suspect, 1*time.Second)
s.Equal(rp.config.StateTimeouts.Faulty, 2*time.Second)
s.Equal(rp.config.StateTimeouts.Tombstone, 3*time.Second)
}

func TestRingpopOptionsTestSuite(t *testing.T) {
suite.Run(t, new(RingpopOptionsTestSuite))
}
17 changes: 15 additions & 2 deletions ringpop.go
Expand Up @@ -164,7 +164,8 @@ func (rp *Ringpop) init() error {
rp.registerHandlers()

rp.node = swim.NewNode(rp.config.App, address, rp.subChannel, &swim.Options{
Clock: rp.clock,
StateTimeouts: rp.config.StateTimeouts,
Clock: rp.clock,
})
rp.node.RegisterListener(rp)

Expand Down Expand Up @@ -313,11 +314,23 @@ func (rp *Ringpop) getState() state {
return r
}

// setState sets the state of the current Ringpop instance.
// setState sets the state of the current Ringpop instance. It will emit an appropriate
// event when the state will actually change
func (rp *Ringpop) setState(s state) {
rp.stateMutex.Lock()
oldState := rp.state
rp.state = s
rp.stateMutex.Unlock()

// test if the state has changed with this call to setState
if oldState != s {
switch s {
case ready:
rp.emit(events.Ready{})
case destroyed:
rp.emit(events.Destroyed{})
}
}
}

//= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Expand Down

0 comments on commit 7fbf9f5

Please sign in to comment.