Skip to content

Commit

Permalink
Ensure TChannel is listening on Bootstrap
Browse files Browse the repository at this point in the history
Improve robustness: add an explicit check to ensure TChannel is
listening before we send join requests.

Fixes #146.
  • Loading branch information
dansimau committed Aug 19, 2016
1 parent ff0a2ac commit 02c420f
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 14 deletions.
4 changes: 4 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ var (
// using port 0 and is not listening (and thus has not been assigned a port by
// the OS).
ErrEphemeralIdentity = errors.New("unable to resolve this node's identity from channel that is not yet listening")

// ErrChannelNotListening is returned on bootstrap if TChannel is not
// listening.
ErrChannelNotListening = errors.New("tchannel is not listening")
)
6 changes: 6 additions & 0 deletions ringpop.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ func (rp *Ringpop) Bootstrap(bootstrapOpts *swim.BootstrapOptions) ([]string, er
}
}

// We shouldn't try to bootstrap if the channel is not listening
if rp.channel.State() != tchannel.ChannelListening {
rp.logger.WithField("channelState", rp.channel.State()).Error(ErrChannelNotListening.Error())
return nil, ErrChannelNotListening
}

joined, err := rp.node.Bootstrap(bootstrapOpts)
if err != nil {
rp.logger.WithField("error", err).Info("bootstrap failed")
Expand Down
58 changes: 46 additions & 12 deletions ringpop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ func (s *RingpopTestSuite) SetupTest() {
s.ringpop, err = New("test", Identity("127.0.0.1:3001"), Channel(ch), Clock(s.mockClock))
s.NoError(err, "Ringpop must create successfully")

ch.ListenAndServe(":0")

s.mockRingpop = &mocks.Ringpop{}
s.mockSwimNode = &mocks.SwimNode{}

Expand Down Expand Up @@ -436,7 +438,8 @@ func (s *RingpopTestSuite) TestStateInitialized() {
// TestStateReady tests that Ringpop is ready after successful bootstrapping.
func (s *RingpopTestSuite) TestStateReady() {
// Bootstrap
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

s.Equal(ready, s.ringpop.state)
}
Expand All @@ -445,7 +448,8 @@ func (s *RingpopTestSuite) TestStateReady() {
// Destroy().
func (s *RingpopTestSuite) TestStateDestroyed() {
// Bootstrap
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

// Destroy
s.ringpop.Destroy()
Expand Down Expand Up @@ -475,7 +479,8 @@ func (s *RingpopTestSuite) TestDestroyFromInitialized() {

// TestDestroyIsIdempotent tests that Destroy() can be called multiple times.
func (s *RingpopTestSuite) TestDestroyIsIdempotent() {
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

s.ringpop.Destroy()
s.Equal(destroyed, s.ringpop.state)
Expand All @@ -493,7 +498,9 @@ func (s *RingpopTestSuite) TestWhoAmI() {
s.Equal("", identity)
s.Error(err)

createSingleNodeCluster(s.ringpop)
err = createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

s.Equal(ready, s.ringpop.state)
identity, err = s.ringpop.WhoAmI()
s.NoError(err)
Expand All @@ -508,7 +515,9 @@ func (s *RingpopTestSuite) TestUptime() {
s.Zero(uptime)
s.Error(err)

createSingleNodeCluster(s.ringpop)
err = createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

s.Equal(ready, s.ringpop.state)
uptime, err = s.ringpop.Uptime()
s.NoError(err)
Expand All @@ -523,7 +532,9 @@ func (s *RingpopTestSuite) TestChecksum() {
s.Zero(checksum)
s.Error(err)

createSingleNodeCluster(s.ringpop)
err = createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

s.Equal(ready, s.ringpop.state)
checksum, err = s.ringpop.Checksum()
s.NoError(err)
Expand All @@ -543,7 +554,8 @@ func (s *RingpopTestSuite) TestLookupNotReady() {
}

func (s *RingpopTestSuite) TestLookupNoDestination() {
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

address, _ := s.ringpop.identity()
s.ringpop.ring.RemoveServer(address)
Expand All @@ -554,7 +566,8 @@ func (s *RingpopTestSuite) TestLookupNoDestination() {
}

func (s *RingpopTestSuite) TestLookupEmitStat() {
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

stats := newDummyStats()
s.ringpop.statter = stats
Expand All @@ -566,7 +579,8 @@ func (s *RingpopTestSuite) TestLookupEmitStat() {
}

func (s *RingpopTestSuite) TestLookupNEmitStat() {
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

stats := newDummyStats()
s.ringpop.statter = stats
Expand All @@ -589,7 +603,8 @@ func (s *RingpopTestSuite) TestLookupNNotReady() {
}

func (s *RingpopTestSuite) TestLookupNNoDestinations() {
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

address, _ := s.ringpop.identity()
s.ringpop.ring.RemoveServer(address)
Expand All @@ -600,7 +615,8 @@ func (s *RingpopTestSuite) TestLookupNNoDestinations() {
}

func (s *RingpopTestSuite) TestLookupN() {
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")

result, err := s.ringpop.LookupN("foo", 5)

Expand Down Expand Up @@ -646,7 +662,8 @@ func (s *RingpopTestSuite) TestAddSelfToBootstrapList() {
// TestEmptyJoinListCreatesSingleNodeCluster tests that when you call Bootstrap
// with no hosts or bootstrap file, a single-node cluster is created.
func (s *RingpopTestSuite) TestEmptyJoinListCreatesSingleNodeCluster() {
createSingleNodeCluster(s.ringpop)
err := createSingleNodeCluster(s.ringpop)
s.Require().NoError(err, "unable to bootstrap single node cluster")
s.Equal(ready, s.ringpop.state)
}

Expand Down Expand Up @@ -790,3 +807,20 @@ func (s *RingpopTestSuite) TestRingChecksumEmitTimer() {
s.True(ok, "ring checksum stat")
s.ringpop.Destroy()
}

func (s *RingpopTestSuite) TestDontAllowBootstrapWithoutChannelListening() {
ch, err := tchannel.NewChannel("test", nil)
s.NoError(err, "channel must create successfully")
s.channel = ch

// Bug #146 meant that you could bootstrap ringpop without a listening
// channel IF you provided the Identity argument (or a custom Identity)
// provider.
s.ringpop, err = New("test", Channel(ch), Identity("127.0.0.1:3001"))
s.NoError(err, "Ringpop must create successfully")

// Calls bootstrap
err = createSingleNodeCluster(s.ringpop)

s.Error(err, "expect error when tchannel is not listening")
}
5 changes: 3 additions & 2 deletions shared/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import "github.com/uber/tchannel-go"

// The TChannel interface defines the dependencies for TChannel in Ringpop.
type TChannel interface {
Register(h tchannel.Handler, methodName string)
PeerInfo() tchannel.LocalPeerInfo
GetSubChannel(string, ...tchannel.SubChannelOption) *tchannel.SubChannel
PeerInfo() tchannel.LocalPeerInfo
Register(h tchannel.Handler, methodName string)
State() tchannel.ChannelState
}

// SubChannel represents a TChannel SubChannel as used in Ringpop.
Expand Down

0 comments on commit 02c420f

Please sign in to comment.