mirror of
https://github.com/octoleo/syncthing.git
synced 2025-02-02 11:58:28 +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"`
|
Addresses []string `xml:"address,omitempty"`
|
||||||
Compression bool `xml:"compression,attr"`
|
Compression bool `xml:"compression,attr"`
|
||||||
CertName string `xml:"certName,attr,omitempty"`
|
CertName string `xml:"certName,attr,omitempty"`
|
||||||
|
Introducer bool `xml:"introducer,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepositoryNodeConfiguration struct {
|
type RepositoryNodeConfiguration struct {
|
||||||
@ -153,15 +154,24 @@ func (cfg *Configuration) NodeMap() map[protocol.NodeID]NodeConfiguration {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Configuration) GetNodeConfiguration(nodeid protocol.NodeID) *NodeConfiguration {
|
func (cfg *Configuration) GetNodeConfiguration(nodeID protocol.NodeID) *NodeConfiguration {
|
||||||
for i, node := range cfg.Nodes {
|
for i, node := range cfg.Nodes {
|
||||||
if node.NodeID == nodeid {
|
if node.NodeID == nodeID {
|
||||||
return &cfg.Nodes[i]
|
return &cfg.Nodes[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
func (cfg *Configuration) RepoMap() map[string]RepositoryConfiguration {
|
||||||
m := make(map[string]RepositoryConfiguration, len(cfg.Repositories))
|
m := make(map[string]RepositoryConfiguration, len(cfg.Repositories))
|
||||||
for _, r := range cfg.Repositories {
|
for _, r := range cfg.Repositories {
|
||||||
|
@ -666,7 +666,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
|||||||
$scope.addNode = function () {
|
$scope.addNode = function () {
|
||||||
$scope.currentNode = {
|
$scope.currentNode = {
|
||||||
AddressesStr: 'dynamic',
|
AddressesStr: 'dynamic',
|
||||||
Compression: true
|
Compression: true,
|
||||||
|
Introducer: true
|
||||||
};
|
};
|
||||||
$scope.editingExisting = false;
|
$scope.editingExisting = false;
|
||||||
$scope.editingSelf = 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">Yes</td>
|
||||||
<td translate ng-if="!nodeCfg.Compression" class="text-right">No</td>
|
<td translate ng-if="!nodeCfg.Compression" class="text-right">No</td>
|
||||||
</tr>
|
</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]">
|
<tr ng-if="connections[nodeCfg.NodeID]">
|
||||||
<th><span class="glyphicon glyphicon-tag"></span> <span translate>Version</span></th>
|
<th><span class="glyphicon glyphicon-tag"></span> <span translate>Version</span></th>
|
||||||
<td class="text-right">{{connections[nodeCfg.NodeID].ClientVersion}}</td>
|
<td class="text-right">{{connections[nodeCfg.NodeID].ClientVersion}}</td>
|
||||||
@ -388,6 +393,15 @@
|
|||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ng-model="currentNode.Compression"> <span translate>Use Compression</span>
|
<input type="checkbox" ng-model="currentNode.Compression"> <span translate>Use Compression</span>
|
||||||
</label>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -8,10 +8,12 @@
|
|||||||
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
|
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
|
||||||
"Announce Server": "Announce Server",
|
"Announce Server": "Announce Server",
|
||||||
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
|
"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",
|
"Bugs": "Bugs",
|
||||||
"CPU Utilization": "CPU Utilization",
|
"CPU Utilization": "CPU Utilization",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
|
"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",
|
"Connection Error": "Connection Error",
|
||||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
|
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
|
||||||
"Delete": "Delete",
|
"Delete": "Delete",
|
||||||
@ -42,6 +44,7 @@
|
|||||||
"Ignore Patterns": "Ignore Patterns",
|
"Ignore Patterns": "Ignore Patterns",
|
||||||
"Ignore Permissions": "Ignore Permissions",
|
"Ignore Permissions": "Ignore Permissions",
|
||||||
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
"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)",
|
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
|
||||||
"Keep Versions": "Keep Versions",
|
"Keep Versions": "Keep Versions",
|
||||||
"Last seen": "Last seen",
|
"Last seen": "Last seen",
|
||||||
|
@ -437,18 +437,18 @@ func (m *Model) repoSharedWith(repo string, nodeID protocol.NodeID) bool {
|
|||||||
return false
|
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()
|
m.pmut.Lock()
|
||||||
if config.ClientName == "syncthing" {
|
if cm.ClientName == "syncthing" {
|
||||||
m.nodeVer[nodeID] = config.ClientVersion
|
m.nodeVer[nodeID] = cm.ClientVersion
|
||||||
} else {
|
} else {
|
||||||
m.nodeVer[nodeID] = config.ClientName + " " + config.ClientVersion
|
m.nodeVer[nodeID] = cm.ClientName + " " + cm.ClientVersion
|
||||||
}
|
}
|
||||||
m.pmut.Unlock()
|
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)
|
l.Infof("Node %s hostname is %q", nodeID, name)
|
||||||
node := m.cfg.GetNodeConfiguration(nodeID)
|
node := m.cfg.GetNodeConfiguration(nodeID)
|
||||||
if node != nil && node.Name == "" {
|
if node != nil && node.Name == "" {
|
||||||
@ -456,6 +456,73 @@ func (m *Model) ClusterConfig(nodeID protocol.NodeID, config protocol.ClusterCon
|
|||||||
m.cfg.Save()
|
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.
|
// 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[:]
|
// so we don't grab aliases to the same array later on in node[:]
|
||||||
node := node
|
node := node
|
||||||
// TODO: Set read only bit when relevant
|
// TODO: Set read only bit when relevant
|
||||||
cr.Nodes = append(cr.Nodes, protocol.Node{
|
cn := protocol.Node{
|
||||||
ID: node[:],
|
ID: node[:],
|
||||||
Flags: protocol.FlagShareTrusted,
|
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)
|
cm.Repositories = append(cm.Repositories, cr)
|
||||||
}
|
}
|
||||||
|
@ -307,6 +307,7 @@ func TestClusterConfig(t *testing.T) {
|
|||||||
cfg.Nodes = []config.NodeConfiguration{
|
cfg.Nodes = []config.NodeConfiguration{
|
||||||
{
|
{
|
||||||
NodeID: node1,
|
NodeID: node1,
|
||||||
|
Introducer: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NodeID: node2,
|
NodeID: node2,
|
||||||
@ -351,9 +352,15 @@ func TestClusterConfig(t *testing.T) {
|
|||||||
if id := r.Nodes[0].ID; bytes.Compare(id, node1[:]) != 0 {
|
if id := r.Nodes[0].ID; bytes.Compare(id, node1[:]) != 0 {
|
||||||
t.Errorf("Incorrect node ID %x != %x", id, node1)
|
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 {
|
if id := r.Nodes[1].ID; bytes.Compare(id, node2[:]) != 0 {
|
||||||
t.Errorf("Incorrect node ID %x != %x", id, node2)
|
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]
|
r = cm.Repositories[1]
|
||||||
if r.ID != "repo2" {
|
if r.ID != "repo2" {
|
||||||
@ -365,9 +372,15 @@ func TestClusterConfig(t *testing.T) {
|
|||||||
if id := r.Nodes[0].ID; bytes.Compare(id, node1[:]) != 0 {
|
if id := r.Nodes[0].ID; bytes.Compare(id, node1[:]) != 0 {
|
||||||
t.Errorf("Incorrect node ID %x != %x", id, node1)
|
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 {
|
if id := r.Nodes[1].ID; bytes.Compare(id, node2[:]) != 0 {
|
||||||
t.Errorf("Incorrect node ID %x != %x", id, node2)
|
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) {
|
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
|
||||||
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
|
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
|
- 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
|
- Bit 30 ("R", Read Only) is set for nodes that participate in read
|
||||||
only mode.
|
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 16 through 28 are reserved and MUST be set to zero.
|
||||||
|
|
||||||
- Bits 14-15 ("Pri) indicate the node's upload priority for this
|
- 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.
|
- 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
|
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
|
version number of the files already known to be in the index sent by
|
||||||
|
@ -47,6 +47,7 @@ const (
|
|||||||
const (
|
const (
|
||||||
FlagShareTrusted uint32 = 1 << 0
|
FlagShareTrusted uint32 = 1 << 0
|
||||||
FlagShareReadOnly = 1 << 1
|
FlagShareReadOnly = 1 << 1
|
||||||
|
FlagIntroducer = 1 << 2
|
||||||
FlagShareBits = 0x000000ff
|
FlagShareBits = 0x000000ff
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user