Add "cluster introducer" functionality to nodes (ref #120)

This commit is contained in:
Jakob Borg
2014-09-23 16:04:20 +02:00
parent 24e5000c37
commit e596a45e9f
9 changed files with 133 additions and 17 deletions
+3 -3
View File
File diff suppressed because one or more lines are too long
+12 -2
View File
@@ -101,6 +101,7 @@ type NodeConfiguration struct {
Addresses []string `xml:"address,omitempty"`
Compression bool `xml:"compression,attr"`
CertName string `xml:"certName,attr,omitempty"`
Introducer bool `xml:"introducer,attr"`
}
type RepositoryNodeConfiguration struct {
@@ -153,15 +154,24 @@ func (cfg *Configuration) NodeMap() map[protocol.NodeID]NodeConfiguration {
return m
}
func (cfg *Configuration) GetNodeConfiguration(nodeid protocol.NodeID) *NodeConfiguration {
func (cfg *Configuration) GetNodeConfiguration(nodeID protocol.NodeID) *NodeConfiguration {
for i, node := range cfg.Nodes {
if node.NodeID == nodeid {
if node.NodeID == nodeID {
return &cfg.Nodes[i]
}
}
return nil
}
func (cfg *Configuration) GetRepoConfiguration(repoID string) *RepositoryConfiguration {
for i, repo := range cfg.Repositories {
if repo.ID == repoID {
return &cfg.Repositories[i]
}
}
return nil
}
func (cfg *Configuration) RepoMap() map[string]RepositoryConfiguration {
m := make(map[string]RepositoryConfiguration, len(cfg.Repositories))
for _, r := range cfg.Repositories {
+2 -1
View File
@@ -666,7 +666,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
$scope.addNode = function () {
$scope.currentNode = {
AddressesStr: 'dynamic',
Compression: true
Compression: true,
Introducer: true
};
$scope.editingExisting = false;
$scope.editingSelf = false;
+14
View File
@@ -260,6 +260,11 @@
<td translate ng-if="nodeCfg.Compression" class="text-right">Yes</td>
<td translate ng-if="!nodeCfg.Compression" class="text-right">No</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-thumbs-up"></span>&emsp;<span translate>Introducer</span></th>
<td translate ng-if="nodeCfg.Introducer" class="text-right">Yes</td>
<td translate ng-if="!nodeCfg.Introducer" class="text-right">No</td>
</tr>
<tr ng-if="connections[nodeCfg.NodeID]">
<th><span class="glyphicon glyphicon-tag"></span>&emsp;<span translate>Version</span></th>
<td class="text-right">{{connections[nodeCfg.NodeID].ClientVersion}}</td>
@@ -388,6 +393,15 @@
<label>
<input type="checkbox" ng-model="currentNode.Compression"> <span translate>Use Compression</span>
</label>
<p translate class="help-block">Compression is recommended in most setups.</p>
</div>
</div>
<div ng-if="!editingSelf" class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentNode.Introducer"> <span translate>Introducer</span>
</label>
<p translate class="help-block">Any nodes configured on an introducer node will be added to this node as well.</p>
</div>
</div>
</form>
+3
View File
@@ -8,10 +8,12 @@
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"Announce Server": "Announce Server",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Any nodes configured on an introducer node will be added to this node as well.": "Any nodes configured on an introducer node will be added to this node as well.",
"Bugs": "Bugs",
"CPU Utilization": "CPU Utilization",
"Close": "Close",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
"Connection Error": "Connection Error",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
"Delete": "Delete",
@@ -42,6 +44,7 @@
"Ignore Patterns": "Ignore Patterns",
"Ignore Permissions": "Ignore Permissions",
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"Last seen": "Last seen",
+79 -8
View File
@@ -437,18 +437,18 @@ func (m *Model) repoSharedWith(repo string, nodeID protocol.NodeID) bool {
return false
}
func (m *Model) ClusterConfig(nodeID protocol.NodeID, config protocol.ClusterConfigMessage) {
func (m *Model) ClusterConfig(nodeID protocol.NodeID, cm protocol.ClusterConfigMessage) {
m.pmut.Lock()
if config.ClientName == "syncthing" {
m.nodeVer[nodeID] = config.ClientVersion
if cm.ClientName == "syncthing" {
m.nodeVer[nodeID] = cm.ClientVersion
} else {
m.nodeVer[nodeID] = config.ClientName + " " + config.ClientVersion
m.nodeVer[nodeID] = cm.ClientName + " " + cm.ClientVersion
}
m.pmut.Unlock()
l.Infof(`Node %s client is "%s %s"`, nodeID, config.ClientName, config.ClientVersion)
l.Infof(`Node %s client is "%s %s"`, nodeID, cm.ClientName, cm.ClientVersion)
if name := config.GetOption("name"); name != "" {
if name := cm.GetOption("name"); name != "" {
l.Infof("Node %s hostname is %q", nodeID, name)
node := m.cfg.GetNodeConfiguration(nodeID)
if node != nil && node.Name == "" {
@@ -456,6 +456,73 @@ func (m *Model) ClusterConfig(nodeID protocol.NodeID, config protocol.ClusterCon
m.cfg.Save()
}
}
if m.cfg.GetNodeConfiguration(nodeID).Introducer {
// This node is an introducer. Go through the announced lists of repos
// and nodes and add what we are missing.
var changed bool
for _, repo := range cm.Repositories {
// If we don't have this repository yet, skip it. Ideally, we'd
// offer up something in the GUI to create the repo, but for the
// moment we only handle repos that we already have.
if _, ok := m.repoNodes[repo.ID]; !ok {
continue
}
nextNode:
for _, node := range repo.Nodes {
var id protocol.NodeID
copy(id[:], node.ID)
if _, ok := m.nodeRepos[id]; !ok {
// The node is currently unknown. Add it to the config.
l.Infof("Adding node %v to config (vouched for by introducer %v)", id, nodeID)
newNodeCfg := config.NodeConfiguration{
NodeID: id,
}
// The introducers' introducers are also our introducers.
if node.Flags&protocol.FlagIntroducer != 0 {
l.Infof("Node %v is now also an introducer", id)
newNodeCfg.Introducer = true
}
m.cfg.Nodes = append(m.cfg.Nodes, newNodeCfg)
changed = true
}
for _, er := range m.nodeRepos[id] {
if er == repo.ID {
// We already share the repo with this node, so
// nothing to do.
continue nextNode
}
}
// We don't yet share this repo with this node. Add the node
// to sharing list of the repo.
l.Infof("Adding node %v to share %q (vouched for by introducer %v)", id, repo.ID, nodeID)
m.nodeRepos[id] = append(m.nodeRepos[id], repo.ID)
m.repoNodes[repo.ID] = append(m.repoNodes[repo.ID], id)
repoCfg := m.cfg.GetRepoConfiguration(repo.ID)
repoCfg.Nodes = append(repoCfg.Nodes, config.RepositoryNodeConfiguration{
NodeID: id,
})
changed = true
}
}
if changed {
m.cfg.Save()
}
}
}
// Close removes the peer from the model and closes the underlying connection if possible.
@@ -1030,10 +1097,14 @@ func (m *Model) clusterConfig(node protocol.NodeID) protocol.ClusterConfigMessag
// so we don't grab aliases to the same array later on in node[:]
node := node
// TODO: Set read only bit when relevant
cr.Nodes = append(cr.Nodes, protocol.Node{
cn := protocol.Node{
ID: node[:],
Flags: protocol.FlagShareTrusted,
})
}
if nodeCfg := m.cfg.GetNodeConfiguration(node); nodeCfg.Introducer {
cn.Flags |= protocol.FlagIntroducer
}
cr.Nodes = append(cr.Nodes, cn)
}
cm.Repositories = append(cm.Repositories, cr)
}
+14 -1
View File
@@ -306,7 +306,8 @@ func TestClusterConfig(t *testing.T) {
cfg := config.New("/tmp/test", node1)
cfg.Nodes = []config.NodeConfiguration{
{
NodeID: node1,
NodeID: node1,
Introducer: true,
},
{
NodeID: node2,
@@ -351,9 +352,15 @@ func TestClusterConfig(t *testing.T) {
if id := r.Nodes[0].ID; bytes.Compare(id, node1[:]) != 0 {
t.Errorf("Incorrect node ID %x != %x", id, node1)
}
if r.Nodes[0].Flags&protocol.FlagIntroducer == 0 {
t.Error("Node1 should be flagged as Introducer")
}
if id := r.Nodes[1].ID; bytes.Compare(id, node2[:]) != 0 {
t.Errorf("Incorrect node ID %x != %x", id, node2)
}
if r.Nodes[1].Flags&protocol.FlagIntroducer != 0 {
t.Error("Node2 should not be flagged as Introducer")
}
r = cm.Repositories[1]
if r.ID != "repo2" {
@@ -365,9 +372,15 @@ func TestClusterConfig(t *testing.T) {
if id := r.Nodes[0].ID; bytes.Compare(id, node1[:]) != 0 {
t.Errorf("Incorrect node ID %x != %x", id, node1)
}
if r.Nodes[0].Flags&protocol.FlagIntroducer == 0 {
t.Error("Node1 should be flagged as Introducer")
}
if id := r.Nodes[1].ID; bytes.Compare(id, node2[:]) != 0 {
t.Errorf("Incorrect node ID %x != %x", id, node2)
}
if r.Nodes[1].Flags&protocol.FlagIntroducer != 0 {
t.Error("Node2 should not be flagged as Introducer")
}
}
func TestIgnores(t *testing.T) {
+5 -2
View File
@@ -249,7 +249,7 @@ The Node Flags field contains the following single bit flags:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved |Pri| Reserved |R|T|
| Reserved |Pri| Reserved |I|R|T|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- Bit 31 ("T", Trusted) is set for nodes that participate in trusted
@@ -258,6 +258,9 @@ The Node Flags field contains the following single bit flags:
- Bit 30 ("R", Read Only) is set for nodes that participate in read
only mode.
- Bit 29 ("I", Introducer) is set for nodes that are trusted as cluster
introducers.
- Bits 16 through 28 are reserved and MUST be set to zero.
- Bits 14-15 ("Pri) indicate the node's upload priority for this
@@ -276,7 +279,7 @@ The Node Flags field contains the following single bit flags:
- Bits 0 through 14 are reserved and MUST be set to zero.
Exactly one of the T, R or S bits MUST be set.
Exactly one of the T and R bits MUST be set.
The per node Max Local Version field contains the highest local file
version number of the files already known to be in the index sent by
+1
View File
@@ -47,6 +47,7 @@ const (
const (
FlagShareTrusted uint32 = 1 << 0
FlagShareReadOnly = 1 << 1
FlagIntroducer = 1 << 2
FlagShareBits = 0x000000ff
)