Просмотр исходного кода

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

Jakob Borg 11 лет назад
Родитель
Сommit
e596a45e9f
9 измененных файлов с 130 добавлено и 14 удалено
  1. 0 0
      auto/gui.files.go
  2. 12 2
      config/config.go
  3. 2 1
      gui/app.js
  4. 14 0
      gui/index.html
  5. 3 0
      gui/lang/lang-en.json
  6. 79 8
      model/model.go
  7. 14 1
      model/model_test.go
  8. 5 2
      protocol/PROTOCOL.md
  9. 1 0
      protocol/protocol.go

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
auto/gui.files.go


+ 12 - 2
config/config.go

@@ -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
gui/app.js

@@ -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 - 0
gui/index.html

@@ -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 - 0
gui/lang/lang-en.json

@@ -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
model/model.go

@@ -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
model/model_test.go

@@ -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
protocol/PROTOCOL.md

@@ -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 - 0
protocol/protocol.go

@@ -47,6 +47,7 @@ const (
 const (
 	FlagShareTrusted  uint32 = 1 << 0
 	FlagShareReadOnly        = 1 << 1
+	FlagIntroducer           = 1 << 2
 	FlagShareBits            = 0x000000ff
 )
 

Некоторые файлы не были показаны из-за большого количества измененных файлов