File
Blob: chord/local_membership_test.go
| 1 | package chord |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "testing" |
| 6 | |
| 7 | "go.miragespace.co/specter/spec/chord" |
| 8 | |
| 9 | "github.com/stretchr/testify/require" |
| 10 | ) |
| 11 | |
| 12 | func TestJoinAfterMissingPredecessor(t *testing.T) { |
| 13 | as := require.New(t) |
| 14 | ctx := context.Background() |
| 15 | key, value := []byte("transferred"), []byte("move") |
| 16 | |
| 17 | nodeConfig := devConfig(t, as) |
| 18 | nodeConfig.Identity.Id = chord.Hash([]byte("owner")) |
| 19 | node := NewLocalNode(nodeConfig) |
| 20 | joinerConfig := devConfig(t, as) |
| 21 | joinerConfig.Identity.Id = chord.Hash(key) |
| 22 | joiner := NewLocalNode(joinerConfig) |
| 23 | as.NotEqual(node.ID(), joiner.ID()) |
| 24 | |
| 25 | // Keep maintenance tasks stopped so they cannot repair the missing |
| 26 | // predecessor before the join request exercises the temporary gap. |
| 27 | node.successors = []chord.VNode{node} |
| 28 | node.succListHash.Store(node.hash(node.successors)) |
| 29 | for i := 1; i <= chord.MaxFingerEntries; i++ { |
| 30 | node.fingers[i].node = node |
| 31 | } |
| 32 | node.state.Set(chord.Active) |
| 33 | as.NoError(node.kv.Put(ctx, key, value)) |
| 34 | |
| 35 | predecessor, successors, err := node.RequestToJoin(joiner) |
| 36 | as.ErrorIs(err, chord.ErrJoinInvalidState) |
| 37 | as.True(chord.ErrorIsRetryable(err)) |
| 38 | as.Nil(predecessor) |
| 39 | as.Nil(successors) |
| 40 | as.Equal(chord.Active, node.state.Get()) |
| 41 | as.Nil(node.getPredecessor()) |
| 42 | as.Nil(node.surrogate) |
| 43 | |
| 44 | stored, err := node.kv.Get(ctx, key) |
| 45 | as.NoError(err) |
| 46 | as.Equal(value, stored) |
| 47 | keys, err := joiner.kv.RangeKeys(ctx, 0, 0) |
| 48 | as.NoError(err) |
| 49 | as.Empty(keys) |
| 50 | |
| 51 | // A subsequent notification establishes the range boundary and allows a |
| 52 | // normal join, including transfer and membership-lock release, to finish. |
| 53 | as.NoError(node.Notify(node)) |
| 54 | as.NoError(joiner.Join(node)) |
| 55 | defer joiner.Leave() |
| 56 | as.Equal(chord.Active, node.state.Get()) |
| 57 | as.Equal(chord.Active, joiner.state.Get()) |
| 58 | |
| 59 | keys, err = node.kv.RangeKeys(ctx, 0, 0) |
| 60 | as.NoError(err) |
| 61 | as.Empty(keys) |
| 62 | stored, err = joiner.kv.Get(ctx, key) |
| 63 | as.NoError(err) |
| 64 | as.Equal(value, stored) |
| 65 | } |