Refactor node ID handling, use check digits (fixes #269)

New node ID:s contain four Luhn check digits and are grouped
differently. Code uses NodeID type instead of string, so it's formatted
homogenously everywhere.
This commit is contained in:
Jakob Borg
2014-06-30 01:42:03 +02:00
parent fee8289c0a
commit 8f3effed32
35 changed files with 563 additions and 478 deletions

View File

@@ -49,8 +49,8 @@ type Model struct {
repoCfgs map[string]config.RepositoryConfiguration // repo -> cfg
repoFiles map[string]*files.Set // repo -> files
repoNodes map[string][]string // repo -> nodeIDs
nodeRepos map[string][]string // nodeID -> repos
repoNodes map[string][]protocol.NodeID // repo -> nodeIDs
nodeRepos map[protocol.NodeID][]string // nodeID -> repos
suppressor map[string]*suppressor // repo -> suppressor
rmut sync.RWMutex // protects the above
@@ -59,9 +59,9 @@ type Model struct {
cm *cid.Map
protoConn map[string]protocol.Connection
rawConn map[string]io.Closer
nodeVer map[string]string
protoConn map[protocol.NodeID]protocol.Connection
rawConn map[protocol.NodeID]io.Closer
nodeVer map[protocol.NodeID]string
pmut sync.RWMutex // protects protoConn and rawConn
sup suppressor
@@ -86,14 +86,14 @@ func NewModel(indexDir string, cfg *config.Configuration, clientName, clientVers
clientVersion: clientVersion,
repoCfgs: make(map[string]config.RepositoryConfiguration),
repoFiles: make(map[string]*files.Set),
repoNodes: make(map[string][]string),
nodeRepos: make(map[string][]string),
repoNodes: make(map[string][]protocol.NodeID),
nodeRepos: make(map[protocol.NodeID][]string),
repoState: make(map[string]repoState),
suppressor: make(map[string]*suppressor),
cm: cid.NewMap(),
protoConn: make(map[string]protocol.Connection),
rawConn: make(map[string]io.Closer),
nodeVer: make(map[string]string),
protoConn: make(map[protocol.NodeID]protocol.Connection),
rawConn: make(map[protocol.NodeID]io.Closer),
nodeVer: make(map[protocol.NodeID]string),
sup: suppressor{threshold: int64(cfg.Options.MaxChangeKbps)},
}
@@ -182,7 +182,7 @@ func (m *Model) ConnectionStats() map[string]ConnectionInfo {
ci.Completion = int(100 * have / tot)
}
res[node] = ci
res[node.String()] = ci
}
m.rmut.RUnlock()
@@ -261,7 +261,7 @@ func (m *Model) NeedFilesRepo(repo string) []scanner.File {
// Index is called when a new node is connected and we receive their full index.
// Implements the protocol.Model interface.
func (m *Model) Index(nodeID string, repo string, fs []protocol.FileInfo) {
func (m *Model) Index(nodeID protocol.NodeID, repo string, fs []protocol.FileInfo) {
if debug {
l.Debugf("IDX(in): %s %q: %d files", nodeID, repo, len(fs))
}
@@ -297,7 +297,7 @@ func (m *Model) Index(nodeID string, repo string, fs []protocol.FileInfo) {
// IndexUpdate is called for incremental updates to connected nodes' indexes.
// Implements the protocol.Model interface.
func (m *Model) IndexUpdate(nodeID string, repo string, fs []protocol.FileInfo) {
func (m *Model) IndexUpdate(nodeID protocol.NodeID, repo string, fs []protocol.FileInfo) {
if debug {
l.Debugf("IDXUP(in): %s / %q: %d files", nodeID, repo, len(fs))
}
@@ -331,7 +331,7 @@ func (m *Model) IndexUpdate(nodeID string, repo string, fs []protocol.FileInfo)
m.rmut.RUnlock()
}
func (m *Model) repoSharedWith(repo, nodeID string) bool {
func (m *Model) repoSharedWith(repo string, nodeID protocol.NodeID) bool {
m.rmut.RLock()
defer m.rmut.RUnlock()
for _, nrepo := range m.nodeRepos[nodeID] {
@@ -342,7 +342,7 @@ func (m *Model) repoSharedWith(repo, nodeID string) bool {
return false
}
func (m *Model) ClusterConfig(nodeID string, config protocol.ClusterConfigMessage) {
func (m *Model) ClusterConfig(nodeID protocol.NodeID, config protocol.ClusterConfigMessage) {
compErr := compareClusterConfig(m.clusterConfig(nodeID), config)
if debug {
l.Debugf("ClusterConfig: %s: %#v", nodeID, config)
@@ -365,7 +365,7 @@ func (m *Model) ClusterConfig(nodeID string, config protocol.ClusterConfigMessag
// Close removes the peer from the model and closes the underlying connection if possible.
// Implements the protocol.Model interface.
func (m *Model) Close(node string, err error) {
func (m *Model) Close(node protocol.NodeID, err error) {
if debug {
l.Debugf("%s: %v", node, err)
}
@@ -397,7 +397,7 @@ func (m *Model) Close(node string, err error) {
// Request returns the specified data segment by reading it from local disk.
// Implements the protocol.Model interface.
func (m *Model) Request(nodeID, repo, name string, offset int64, size int) ([]byte, error) {
func (m *Model) Request(nodeID protocol.NodeID, repo, name string, offset int64, size int) ([]byte, error) {
// Verify that the requested file exists in the local model.
m.rmut.RLock()
r, ok := m.repoFiles[repo]
@@ -423,7 +423,7 @@ func (m *Model) Request(nodeID, repo, name string, offset int64, size int) ([]by
return nil, ErrNoSuchFile
}
if debug && nodeID != "<local>" {
if debug && nodeID != cid.LocalNodeID {
l.Debugf("REQ(in): %s: %q / %q o=%d s=%d", nodeID, repo, name, offset, size)
}
m.rmut.RLock()
@@ -489,7 +489,7 @@ func (cf cFiler) CurrentFile(file string) scanner.File {
}
// ConnectedTo returns true if we are connected to the named node.
func (m *Model) ConnectedTo(nodeID string) bool {
func (m *Model) ConnectedTo(nodeID protocol.NodeID) bool {
m.pmut.RLock()
_, ok := m.protoConn[nodeID]
m.pmut.RUnlock()
@@ -560,7 +560,7 @@ func (m *Model) updateLocal(repo string, f scanner.File) {
m.rmut.RUnlock()
}
func (m *Model) requestGlobal(nodeID, repo, name string, offset int64, size int, hash []byte) ([]byte, error) {
func (m *Model) requestGlobal(nodeID protocol.NodeID, repo, name string, offset int64, size int, hash []byte) ([]byte, error) {
m.pmut.RLock()
nc, ok := m.protoConn[nodeID]
m.pmut.RUnlock()
@@ -639,7 +639,7 @@ func (m *Model) AddRepo(cfg config.RepositoryConfiguration) {
m.repoFiles[cfg.ID] = files.NewSet()
m.suppressor[cfg.ID] = &suppressor{threshold: int64(m.cfg.Options.MaxChangeKbps)}
m.repoNodes[cfg.ID] = make([]string, len(cfg.Nodes))
m.repoNodes[cfg.ID] = make([]protocol.NodeID, len(cfg.Nodes))
for i, node := range cfg.Nodes {
m.repoNodes[cfg.ID][i] = node.NodeID
m.nodeRepos[node.NodeID] = append(m.nodeRepos[node.NodeID], cfg.ID)
@@ -805,7 +805,7 @@ func (m *Model) loadIndex(repo string, dir string) []protocol.FileInfo {
}
// clusterConfig returns a ClusterConfigMessage that is correct for the given peer node
func (m *Model) clusterConfig(node string) protocol.ClusterConfigMessage {
func (m *Model) clusterConfig(node protocol.NodeID) protocol.ClusterConfigMessage {
cm := protocol.ClusterConfigMessage{
ClientName: m.clientName,
ClientVersion: m.clientVersion,
@@ -819,7 +819,7 @@ func (m *Model) clusterConfig(node string) protocol.ClusterConfigMessage {
for _, node := range m.repoNodes[repo] {
// TODO: Set read only bit when relevant
cr.Nodes = append(cr.Nodes, protocol.Node{
ID: node,
ID: node[:],
Flags: protocol.FlagShareTrusted,
})
}

View File

@@ -17,6 +17,13 @@ import (
"github.com/calmh/syncthing/scanner"
)
var node1, node2 protocol.NodeID
func init() {
node1, _ = protocol.NodeIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
node2, _ = protocol.NodeIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
}
var testDataExpected = map[string]scanner.File{
"foo": scanner.File{
Name: "foo",
@@ -56,7 +63,7 @@ func TestRequest(t *testing.T) {
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
m.ScanRepo("default")
bs, err := m.Request("some node", "default", "foo", 0, 6)
bs, err := m.Request(node1, "default", "foo", 0, 6)
if err != nil {
t.Fatal(err)
}
@@ -64,7 +71,7 @@ func TestRequest(t *testing.T) {
t.Errorf("Incorrect data from request: %q", string(bs))
}
bs, err = m.Request("some node", "default", "../walk.go", 0, 6)
bs, err = m.Request(node1, "default", "../walk.go", 0, 6)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
@@ -95,7 +102,7 @@ func BenchmarkIndex10000(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Index("42", "default", files)
m.Index(node1, "default", files)
}
}
@@ -107,7 +114,7 @@ func BenchmarkIndex00100(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Index("42", "default", files)
m.Index(node1, "default", files)
}
}
@@ -116,11 +123,11 @@ func BenchmarkIndexUpdate10000f10000(b *testing.B) {
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
m.ScanRepo("default")
files := genFiles(10000)
m.Index("42", "default", files)
m.Index(node1, "default", files)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.IndexUpdate("42", "default", files)
m.IndexUpdate(node1, "default", files)
}
}
@@ -129,12 +136,12 @@ func BenchmarkIndexUpdate10000f00100(b *testing.B) {
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
m.ScanRepo("default")
files := genFiles(10000)
m.Index("42", "default", files)
m.Index(node1, "default", files)
ufiles := genFiles(100)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.IndexUpdate("42", "default", ufiles)
m.IndexUpdate(node1, "default", ufiles)
}
}
@@ -143,17 +150,17 @@ func BenchmarkIndexUpdate10000f00001(b *testing.B) {
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
m.ScanRepo("default")
files := genFiles(10000)
m.Index("42", "default", files)
m.Index(node1, "default", files)
ufiles := genFiles(1)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.IndexUpdate("42", "default", ufiles)
m.IndexUpdate(node1, "default", ufiles)
}
}
type FakeConnection struct {
id string
id protocol.NodeID
requestData []byte
}
@@ -161,8 +168,8 @@ func (FakeConnection) Close() error {
return nil
}
func (f FakeConnection) ID() string {
return string(f.id)
func (f FakeConnection) ID() protocol.NodeID {
return f.id
}
func (f FakeConnection) Option(string) string {
@@ -202,15 +209,15 @@ func BenchmarkRequest(b *testing.B) {
}
fc := FakeConnection{
id: "42",
id: node1,
requestData: []byte("some data to return"),
}
m.AddConnection(fc, fc)
m.Index("42", "default", files)
m.Index(node1, "default", files)
b.ResetTimer()
for i := 0; i < b.N; i++ {
data, err := m.requestGlobal("42", "default", files[i%n].Name, 0, 32, nil)
data, err := m.requestGlobal(node1, "default", files[i%n].Name, 0, 32, nil)
if err != nil {
b.Error(err)
}
@@ -222,26 +229,26 @@ func BenchmarkRequest(b *testing.B) {
func TestActivityMap(t *testing.T) {
cm := cid.NewMap()
fooID := cm.Get("foo")
fooID := cm.Get(node1)
if fooID == 0 {
t.Fatal("ID cannot be zero")
}
barID := cm.Get("bar")
barID := cm.Get(node2)
if barID == 0 {
t.Fatal("ID cannot be zero")
}
m := make(activityMap)
if node := m.leastBusyNode(1<<fooID, cm); node != "foo" {
if node := m.leastBusyNode(1<<fooID, cm); node != node1 {
t.Errorf("Incorrect least busy node %q", node)
}
if node := m.leastBusyNode(1<<barID, cm); node != "bar" {
if node := m.leastBusyNode(1<<barID, cm); node != node2 {
t.Errorf("Incorrect least busy node %q", node)
}
if node := m.leastBusyNode(1<<fooID|1<<barID, cm); node != "foo" {
if node := m.leastBusyNode(1<<fooID|1<<barID, cm); node != node1 {
t.Errorf("Incorrect least busy node %q", node)
}
if node := m.leastBusyNode(1<<fooID|1<<barID, cm); node != "bar" {
if node := m.leastBusyNode(1<<fooID|1<<barID, cm); node != node2 {
t.Errorf("Incorrect least busy node %q", node)
}
}

View File

@@ -21,7 +21,7 @@ import (
)
type requestResult struct {
node string
node protocol.NodeID
file scanner.File
filepath string // full filepath name
offset int64
@@ -39,11 +39,11 @@ type openFile struct {
done bool // we have sent all requests for this file
}
type activityMap map[string]int
type activityMap map[protocol.NodeID]int
func (m activityMap) leastBusyNode(availability uint64, cm *cid.Map) string {
func (m activityMap) leastBusyNode(availability uint64, cm *cid.Map) protocol.NodeID {
var low int = 2<<30 - 1
var selected string
var selected protocol.NodeID
for _, node := range cm.Names() {
id := cm.Get(node)
if id == cid.LocalID {
@@ -61,7 +61,7 @@ func (m activityMap) leastBusyNode(availability uint64, cm *cid.Map) string {
return selected
}
func (m activityMap) decrease(node string) {
func (m activityMap) decrease(node protocol.NodeID) {
m[node]--
}
@@ -540,7 +540,7 @@ func (p *puller) handleRequestBlock(b bqBlock) bool {
of.outstanding++
p.openFiles[f.Name] = of
go func(node string, b bqBlock) {
go func(node protocol.NodeID, b bqBlock) {
if debug {
l.Debugf("pull: requesting %q / %q offset %d size %d from %q outstanding %d", p.repoCfg.ID, f.Name, b.block.Offset, b.block.Size, node, of.outstanding)
}

View File

@@ -58,12 +58,14 @@ func fileInfoFromFile(f scanner.File) protocol.FileInfo {
return pf
}
func cmMap(cm protocol.ClusterConfigMessage) map[string]map[string]uint32 {
m := make(map[string]map[string]uint32)
func cmMap(cm protocol.ClusterConfigMessage) map[string]map[protocol.NodeID]uint32 {
m := make(map[string]map[protocol.NodeID]uint32)
for _, repo := range cm.Repositories {
m[repo.ID] = make(map[string]uint32)
m[repo.ID] = make(map[protocol.NodeID]uint32)
for _, node := range repo.Nodes {
m[repo.ID][node.ID] = node.Flags
var id protocol.NodeID
copy(id[:], node.ID)
m[repo.ID][id] = node.Flags
}
}
return m
@@ -72,7 +74,7 @@ func cmMap(cm protocol.ClusterConfigMessage) map[string]map[string]uint32 {
type ClusterConfigMismatch error
// compareClusterConfig returns nil for two equivalent configurations,
// otherwise a decriptive error
// otherwise a descriptive error
func compareClusterConfig(local, remote protocol.ClusterConfigMessage) error {
lm := cmMap(local)
rm := cmMap(remote)

View File

@@ -1,137 +0,0 @@
// Copyright (C) 2014 Jakob Borg and other contributors. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file.
package model
import (
"testing"
"github.com/calmh/syncthing/protocol"
)
var testcases = []struct {
local, remote protocol.ClusterConfigMessage
err string
}{
{
local: protocol.ClusterConfigMessage{},
remote: protocol.ClusterConfigMessage{},
err: "",
},
{
local: protocol.ClusterConfigMessage{ClientName: "a", ClientVersion: "b"},
remote: protocol.ClusterConfigMessage{ClientName: "c", ClientVersion: "d"},
err: "",
},
{
local: protocol.ClusterConfigMessage{
Repositories: []protocol.Repository{
{ID: "foo"},
{ID: "bar"},
},
},
remote: protocol.ClusterConfigMessage{
Repositories: []protocol.Repository{
{ID: "foo"},
{ID: "bar"},
},
},
err: "",
},
{
local: protocol.ClusterConfigMessage{
Repositories: []protocol.Repository{
{
ID: "foo",
Nodes: []protocol.Node{
{ID: "a"},
},
},
{ID: "bar"},
},
},
remote: protocol.ClusterConfigMessage{
Repositories: []protocol.Repository{
{ID: "foo"},
{ID: "bar"},
},
},
err: "",
},
{
local: protocol.ClusterConfigMessage{
Repositories: []protocol.Repository{
{
ID: "foo",
Nodes: []protocol.Node{
{ID: "a"},
},
},
{ID: "bar"},
},
},
remote: protocol.ClusterConfigMessage{
Repositories: []protocol.Repository{
{
ID: "foo",
Nodes: []protocol.Node{
{ID: "a"},
{ID: "b"},
},
},
{ID: "bar"},
},
},
err: "",
},
{
local: protocol.ClusterConfigMessage{
Repositories: []protocol.Repository{
{
ID: "foo",
Nodes: []protocol.Node{
{
ID: "a",
Flags: protocol.FlagShareReadOnly,
},
},
},
{ID: "bar"},
},
},
remote: protocol.ClusterConfigMessage{
Repositories: []protocol.Repository{
{
ID: "foo",
Nodes: []protocol.Node{
{
ID: "a",
Flags: protocol.FlagShareTrusted,
},
},
},
{ID: "bar"},
},
},
err: `remote has different sharing flags for node "a" in repository "foo"`,
},
}
func TestCompareClusterConfig(t *testing.T) {
for i, tc := range testcases {
err := compareClusterConfig(tc.local, tc.remote)
switch {
case tc.err == "" && err != nil:
t.Errorf("#%d: unexpected error: %v", i, err)
case tc.err != "" && err == nil:
t.Errorf("#%d: unexpected nil error", i)
case tc.err != "" && err != nil && tc.err != err.Error():
t.Errorf("#%d: incorrect error: %q != %q", i, err, tc.err)
}
}
}