2017-10-13 21:12:46 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2018-01-18 02:49:35 +00:00
|
|
|
"io"
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
peer "github.com/libp2p/go-libp2p-peer"
|
|
|
|
dot "github.com/zenground0/go-dot"
|
2017-10-13 21:12:46 +00:00
|
|
|
|
|
|
|
"github.com/ipfs/ipfs-cluster/api"
|
|
|
|
)
|
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
/*
|
|
|
|
These functions are used to write an IPFS Cluster connectivity graph to a
|
|
|
|
graphviz-style dot file. Input an api.ConnectGraphSerial object, makeDot
|
|
|
|
does some preprocessing and then passes all 3 link maps to a
|
|
|
|
cluster-dotWriter which handles iterating over the link maps and writing
|
|
|
|
dot file node and edge statements to make a dot-file graph. Nodes are
|
|
|
|
labeled with the go-libp2p-peer shortened peer id. IPFS nodes are rendered
|
|
|
|
with gold boundaries, Cluster nodes with blue. Currently preprocessing
|
|
|
|
consists of moving IPFS swarm peers not connected to any cluster peer to
|
|
|
|
the IPFSLinks map in the event that the function was invoked with the
|
|
|
|
allIpfs flag. This allows all IPFS peers connected to the cluster to be
|
|
|
|
rendered as nodes in the final graph.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// nodeType specifies the type of node being represented in the dot file:
|
|
|
|
// either IPFS or Cluster
|
2017-10-13 21:12:46 +00:00
|
|
|
type nodeType int
|
|
|
|
|
|
|
|
const (
|
2018-01-18 02:49:35 +00:00
|
|
|
tCluster nodeType = iota // The cluster node type
|
|
|
|
tIpfs // The IPFS node type
|
2017-10-13 21:12:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var errUnfinishedWrite = errors.New("could not complete write of line to output")
|
|
|
|
var errUnknownNodeType = errors.New("unsupported node type. Expected cluster or ipfs")
|
|
|
|
var errCorruptOrdering = errors.New("expected pid to have an ordering within dot writer")
|
|
|
|
|
|
|
|
func makeDot(cg api.ConnectGraphSerial, w io.Writer, allIpfs bool) error {
|
|
|
|
ipfsEdges := make(map[string][]string)
|
2018-01-18 02:49:35 +00:00
|
|
|
for k, v := range cg.IPFSLinks {
|
2017-10-13 21:12:46 +00:00
|
|
|
ipfsEdges[k] = make([]string, 0)
|
|
|
|
for _, id := range v {
|
|
|
|
if _, ok := cg.IPFSLinks[id]; ok || allIpfs {
|
|
|
|
ipfsEdges[k] = append(ipfsEdges[k], id)
|
|
|
|
}
|
|
|
|
if allIpfs { // include all swarm peers in the graph
|
|
|
|
if _, ok := ipfsEdges[id]; !ok {
|
|
|
|
// if id in IPFSLinks this will be overwritten
|
|
|
|
// if id not in IPFSLinks this will stay blank
|
|
|
|
ipfsEdges[id] = make([]string, 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
dW := dotWriter{
|
2017-10-13 21:12:46 +00:00
|
|
|
w: w,
|
2018-01-18 02:49:35 +00:00
|
|
|
dotGraph: dot.NewGraph("cluster"),
|
2017-10-13 21:12:46 +00:00
|
|
|
ipfsEdges: ipfsEdges,
|
|
|
|
clusterEdges: cg.ClusterLinks,
|
|
|
|
clusterIpfsEdges: cg.ClustertoIPFS,
|
2018-01-18 02:49:35 +00:00
|
|
|
clusterNodes: make(map[string]*dot.VertexDescription),
|
|
|
|
ipfsNodes: make(map[string]*dot.VertexDescription),
|
2017-10-13 21:12:46 +00:00
|
|
|
}
|
|
|
|
return dW.print()
|
|
|
|
}
|
|
|
|
|
|
|
|
type dotWriter struct {
|
2018-01-18 02:49:35 +00:00
|
|
|
clusterNodes map[string]*dot.VertexDescription
|
|
|
|
ipfsNodes map[string]*dot.VertexDescription
|
|
|
|
|
|
|
|
w io.Writer
|
2018-09-13 03:29:34 +00:00
|
|
|
dotGraph dot.Graph
|
2017-10-13 21:12:46 +00:00
|
|
|
|
|
|
|
ipfsEdges map[string][]string
|
|
|
|
clusterEdges map[string][]string
|
|
|
|
clusterIpfsEdges map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
// writes nodes to dot file output and creates and stores an ordering over nodes
|
2018-01-18 02:49:35 +00:00
|
|
|
func (dW *dotWriter) addNode(id string, nT nodeType) error {
|
|
|
|
var node dot.VertexDescription
|
|
|
|
node.Label = shortID(id)
|
2017-10-13 21:12:46 +00:00
|
|
|
switch nT {
|
|
|
|
case tCluster:
|
2018-01-18 02:49:35 +00:00
|
|
|
node.ID = fmt.Sprintf("C%d", len(dW.clusterNodes))
|
|
|
|
node.Color = "blue2"
|
|
|
|
dW.clusterNodes[id] = &node
|
2017-10-13 21:12:46 +00:00
|
|
|
case tIpfs:
|
2018-01-18 02:49:35 +00:00
|
|
|
node.ID = fmt.Sprintf("I%d", len(dW.ipfsNodes))
|
|
|
|
node.Color = "goldenrod"
|
|
|
|
dW.ipfsNodes[id] = &node
|
2017-10-13 21:12:46 +00:00
|
|
|
default:
|
|
|
|
return errUnknownNodeType
|
|
|
|
}
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddVertex(&node)
|
|
|
|
|
|
|
|
return nil
|
2017-10-13 21:12:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (dW *dotWriter) print() error {
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddComment("The nodes of the connectivity graph")
|
|
|
|
dW.dotGraph.AddComment("The cluster-service peers")
|
|
|
|
// Write cluster nodes, use sorted order for consistent labels
|
|
|
|
for _, k := range sortedKeys(dW.clusterEdges) {
|
|
|
|
err := dW.addNode(k, tCluster)
|
2017-10-13 21:12:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddNewLine()
|
|
|
|
|
|
|
|
dW.dotGraph.AddComment("The ipfs peers")
|
|
|
|
// Write ipfs nodes, use sorted order for consistent labels
|
|
|
|
for _, k := range sortedKeys(dW.ipfsEdges) {
|
|
|
|
err := dW.addNode(k, tIpfs)
|
2017-10-13 21:12:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddNewLine()
|
2017-10-13 21:12:46 +00:00
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddComment("Edges representing active connections in the cluster")
|
|
|
|
dW.dotGraph.AddComment("The connections among cluster-service peers")
|
2017-10-13 21:12:46 +00:00
|
|
|
// Write cluster edges
|
|
|
|
for k, v := range dW.clusterEdges {
|
|
|
|
for _, id := range v {
|
2018-01-18 02:49:35 +00:00
|
|
|
toNode := dW.clusterNodes[k]
|
|
|
|
fromNode := dW.clusterNodes[id]
|
|
|
|
dW.dotGraph.AddEdge(toNode, fromNode, true)
|
2017-10-13 21:12:46 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddNewLine()
|
2017-10-13 21:12:46 +00:00
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddComment("The connections between cluster peers and their ipfs daemons")
|
2017-10-13 21:12:46 +00:00
|
|
|
// Write cluster to ipfs edges
|
|
|
|
for k, id := range dW.clusterIpfsEdges {
|
2018-01-18 02:49:35 +00:00
|
|
|
toNode := dW.clusterNodes[k]
|
|
|
|
fromNode := dW.ipfsNodes[id]
|
|
|
|
dW.dotGraph.AddEdge(toNode, fromNode, true)
|
2017-10-13 21:12:46 +00:00
|
|
|
}
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddNewLine()
|
2017-10-13 21:12:46 +00:00
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
dW.dotGraph.AddComment("The swarm peer connections among ipfs daemons in the cluster")
|
2017-10-13 21:12:46 +00:00
|
|
|
// Write ipfs edges
|
|
|
|
for k, v := range dW.ipfsEdges {
|
|
|
|
for _, id := range v {
|
2018-01-18 02:49:35 +00:00
|
|
|
toNode := dW.ipfsNodes[k]
|
|
|
|
fromNode := dW.ipfsNodes[id]
|
|
|
|
dW.dotGraph.AddEdge(toNode, fromNode, true)
|
2017-10-13 21:12:46 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-18 02:49:35 +00:00
|
|
|
return dW.dotGraph.WriteDot(dW.w)
|
|
|
|
}
|
2017-10-13 21:12:46 +00:00
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
func sortedKeys(dict map[string][]string) []string {
|
|
|
|
keys := make([]string, len(dict), len(dict))
|
|
|
|
i := 0
|
|
|
|
for k := range dict {
|
|
|
|
keys[i] = k
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
return keys
|
2017-10-13 21:12:46 +00:00
|
|
|
}
|
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
// truncate the provided peer id string to the 3 last characters. Odds of
|
|
|
|
// pairwise collisions are less than 1 in 200,000 so with 70 cluster peers
|
2017-10-13 21:12:46 +00:00
|
|
|
// the chances of a collision are still less than 1 in 100 (birthday paradox).
|
|
|
|
// As clusters grow bigger than this we can provide a flag for including
|
|
|
|
// more characters.
|
2018-01-18 02:49:35 +00:00
|
|
|
func shortID(peerString string) string {
|
|
|
|
pid, err := peer.IDB58Decode(peerString)
|
|
|
|
if err != nil {
|
2018-01-23 13:33:12 +00:00
|
|
|
// Should never get here, panic
|
|
|
|
panic("shortID called on non-pid string")
|
2017-10-13 21:12:46 +00:00
|
|
|
}
|
2018-01-18 02:49:35 +00:00
|
|
|
return pid.String()
|
2017-10-13 21:12:46 +00:00
|
|
|
}
|