Skip to content

Commit

Permalink
1. make the update rules more transparent.
Browse files Browse the repository at this point in the history
2. fixes bug: If a member is in the leave state, only updates where the member
   is made alive are apllied. This means that updates where the member changes
   state to the Suspect, Faulty or Leave state are ignored, even if the
   the updates should be aplied due to having a bigger incarnation number.
  • Loading branch information
Wieger Steggerda committed Feb 10, 2016
1 parent 1156412 commit fdc5c10
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 37 deletions.
1 change: 1 addition & 0 deletions events/test/mocks/event_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type EventListener struct {
mock.Mock
}

// HandleEvent provides a mock function with given fields: event
func (_m *EventListener) HandleEvent(event events.Event) {
_m.Called(event)
}
1 change: 1 addition & 0 deletions forward/mock_event_listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type EventListener struct {
mock.Mock
}

// HandleEvent provides a mock function with given fields: event
func (_m *EventListener) HandleEvent(event events.Event) {
_m.Called(event)
}
29 changes: 29 additions & 0 deletions forward/mock_logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,67 @@ type Logger struct {
mock.Mock
}

// Debug provides a mock function with given fields: args
func (_m *Logger) Debug(args ...interface{}) {
_m.Called(args)
}

// Debugf provides a mock function with given fields: format, args
func (_m *Logger) Debugf(format string, args ...interface{}) {
_m.Called(format, args)
}

// Info provides a mock function with given fields: args
func (_m *Logger) Info(args ...interface{}) {
_m.Called(args)
}

// Infof provides a mock function with given fields: format, args
func (_m *Logger) Infof(format string, args ...interface{}) {
_m.Called(format, args)
}

// Warn provides a mock function with given fields: args
func (_m *Logger) Warn(args ...interface{}) {
_m.Called(args)
}

// Warnf provides a mock function with given fields: format, args
func (_m *Logger) Warnf(format string, args ...interface{}) {
_m.Called(format, args)
}

// Error provides a mock function with given fields: args
func (_m *Logger) Error(args ...interface{}) {
_m.Called(args)
}

// Errorf provides a mock function with given fields: format, args
func (_m *Logger) Errorf(format string, args ...interface{}) {
_m.Called(format, args)
}

// Fatal provides a mock function with given fields: args
func (_m *Logger) Fatal(args ...interface{}) {
_m.Called(args)
}

// Fatalf provides a mock function with given fields: format, args
func (_m *Logger) Fatalf(format string, args ...interface{}) {
_m.Called(format, args)
}

// Panic provides a mock function with given fields: args
func (_m *Logger) Panic(args ...interface{}) {
_m.Called(args)
}

// Panicf provides a mock function with given fields: format, args
func (_m *Logger) Panicf(format string, args ...interface{}) {
_m.Called(format, args)
}

// WithField provides a mock function with given fields: key, value
func (_m *Logger) WithField(key string, value interface{}) bark.Logger {
ret := _m.Called(key, value)

Expand All @@ -55,6 +80,8 @@ func (_m *Logger) WithField(key string, value interface{}) bark.Logger {

return r0
}

// WithFields provides a mock function with given fields: keyValues
func (_m *Logger) WithFields(keyValues bark.LogFields) bark.Logger {
ret := _m.Called(keyValues)

Expand All @@ -67,6 +94,8 @@ func (_m *Logger) WithFields(keyValues bark.LogFields) bark.Logger {

return r0
}

// Fields provides a mock function with given fields:
func (_m *Logger) Fields() bark.Fields {
ret := _m.Called()

Expand Down
3 changes: 3 additions & 0 deletions forward/mock_sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type MockSender struct {
mock.Mock
}

// WhoAmI provides a mock function with given fields:
func (_m *MockSender) WhoAmI() (string, error) {
ret := _m.Called()

Expand All @@ -25,6 +26,8 @@ func (_m *MockSender) WhoAmI() (string, error) {

return r0, r1
}

// Lookup provides a mock function with given fields: _a0
func (_m *MockSender) Lookup(_a0 string) (string, error) {
ret := _m.Called(_a0)

Expand Down
74 changes: 37 additions & 37 deletions swim/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ const (
// Alive is the member "alive" state
Alive = "alive"

// Suspect is the member "suspect" state
Suspect = "suspect"

// Faulty is the member "faulty" state
Faulty = "faulty"

// Leave is the member "leave" state
Leave = "leave"

// Suspect is the member "suspect" state
Suspect = "suspect"
)

// A Member is a member in the member list
Expand Down Expand Up @@ -70,46 +70,46 @@ func shuffle(members []*Member) []*Member {
return newMembers
}

// nonLocalOverride returns wether a change should be applied to the member.
// This function assumes that the address of the member and the change are
// equal.
func (m *Member) nonLocalOverride(change Change) bool {
return m.aliveOverride(change) ||
m.suspectOverride(change) ||
m.faultyOverride(change) ||
m.leaveOverride(change)
}

func (m *Member) aliveOverride(change Change) bool {
return change.Status == Alive && change.Incarnation > m.Incarnation
}

func (m *Member) faultyOverride(change Change) bool {
return change.Status == Faulty &&
((m.Status == Suspect && change.Incarnation >= m.Incarnation) ||
(m.Status == Faulty && change.Incarnation > m.Incarnation) ||
(m.Status == Alive && change.Incarnation >= m.Incarnation))
}
// change is younger than current member
if change.Incarnation > m.Incarnation {
return true
}

func (m *Member) leaveOverride(change Change) bool {
return change.Status == Leave &&
m.Status != Leave && change.Incarnation >= m.Incarnation
}
// change is older than current member
if change.Incarnation < m.Incarnation {
return false
}

func (m *Member) suspectOverride(change Change) bool {
return change.Status == Suspect &&
((m.Status == Suspect && change.Incarnation > m.Incarnation) ||
(m.Status == Faulty && change.Incarnation > m.Incarnation) ||
(m.Status == Alive && change.Incarnation >= m.Incarnation))
// If the incarnation numbers are equal, we look at the state to
// determine wether the change overrides this member.
return statePrecedence(change.Status) > statePrecedence(m.Status)
}

// localOverride returns whether a change should be applied to to the member
func (m *Member) localOverride(local string, change Change) bool {
return m.localSuspectOverride(local, change) || m.localFaultyOverride(local, change)
}

func (m *Member) localFaultyOverride(local string, change Change) bool {
return m.Address == local && change.Status == Faulty
}

func (m *Member) localSuspectOverride(local string, change Change) bool {
return m.Address == local && change.Status == Suspect
if m.Address != local {
return false
}
return change.Status == Faulty || change.Status == Suspect
}

func statePrecedence(s string) int {
switch s {
case Alive:
return 0
case Suspect:
return 1
case Faulty:
return 2
case Leave:
return 3
default:
panic("invalid state")
}
}

func (m *Member) isReachable() bool {
Expand Down
121 changes: 121 additions & 0 deletions swim/member_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) 2016 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 swim

import (
"testing"

"github.com/stretchr/testify/suite"
"github.com/uber/ringpop-go/util"
)

type MemberTestSuite struct {
suite.Suite
states []state
localAddr string
nonLocalAddr string
}

type state struct {
incNum int64
status string
}

func (s *MemberTestSuite) SetupTest() {
s.localAddr = "local address"
s.nonLocalAddr = "non-local address"

incNumStart := util.TimeNowMS()
statuses := []string{Alive, Suspect, Faulty, Leave}

// Add incNo, status combinations of ever increasing precedence.
s.states = nil
for i := int64(0); i < 4; i++ {
for _, status := range statuses {
s.states = append(s.states, state{incNumStart + i, status})
}
}
}

func newMember(addr string, s state) Member {
return Member{
Address: addr,
Status: s.status,
Incarnation: s.incNum,
}
}

func newChange(addr string, s state) Change {
return Change{
Address: addr,
Status: s.status,
Incarnation: s.incNum,
}
}

func (s *MemberTestSuite) TestNonLocalOverride() {
// NonLocalOverride ignores the locallity and only cares about the
// incarnation number and status of the members and changes. Since
// the state (incNum, status pairs) slice is generated with ever
// increasing precendence, changes with index j override members
// with index i if and only if j > i.
for i, s1 := range s.states {
for j, s2 := range s.states {
m := newMember(s.localAddr, s1)
c := newChange(s.localAddr, s2)
expected := j > i
got := m.nonLocalOverride(c)
s.Equal(expected, got, "expected override if and only if j > i")

m = newMember(s.nonLocalAddr, s1)
c = newChange(s.nonLocalAddr, s2)
expected = j > i
got = m.nonLocalOverride(c)
s.Equal(expected, got, "expected override if and only if j > i")
}
}
}

func (s *MemberTestSuite) TestLocalOverride() {
// LocalOverride aggressively marks updates as overrides, even if the
// incarnation number of the update is lower than the incarnation
// number of the local member. The Update function reincarnates the node
// when LocalOverride returns true. This very aggressive approach is likely
// to change in the near future.
for _, s1 := range s.states {
for _, s2 := range s.states {
m := newMember(s.localAddr, s1)
c := newChange(s.localAddr, s2)
expected := c.Status == Suspect || c.Status == Faulty
got := m.localOverride(s.localAddr, c)
s.Equal(expected, got, "expected override when change.Status is suspect or faulty")

m = newMember(s.nonLocalAddr, s1)
c = newChange(s.nonLocalAddr, s2)
got = m.localOverride(s.localAddr, c)
s.False(got, "expected no override since member is not local")
}
}
}

func TestMemberTestSuite(t *testing.T) {
suite.Run(t, new(MemberTestSuite))
}
3 changes: 3 additions & 0 deletions test/mocks/client_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type ClientFactory struct {
mock.Mock
}

// GetLocalClient provides a mock function with given fields:
func (_m *ClientFactory) GetLocalClient() interface{} {
ret := _m.Called()

Expand All @@ -22,6 +23,8 @@ func (_m *ClientFactory) GetLocalClient() interface{} {

return r0
}

// MakeRemoteClient provides a mock function with given fields: client
func (_m *ClientFactory) MakeRemoteClient(client thrift.TChanClient) interface{} {
ret := _m.Called(client)

Expand Down

0 comments on commit fdc5c10

Please sign in to comment.