mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-22 14:48:30 +00:00
Add "cluster introducer" functionality to nodes (ref #120)
This commit is contained in:
parent
24e5000c37
commit
e596a45e9f
File diff suppressed because one or more lines are too long
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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> <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> <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>
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -47,6 +47,7 @@ const (
|
||||
const (
|
||||
FlagShareTrusted uint32 = 1 << 0
|
||||
FlagShareReadOnly = 1 << 1
|
||||
FlagIntroducer = 1 << 2
|
||||
FlagShareBits = 0x000000ff
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user