mirror of
https://github.com/tim-beatham/smegmesh.git
synced 2025-07-03 05:50:22 +02:00
Compare commits
17 Commits
61-improve
...
60-unit-te
Author | SHA1 | Date | |
---|---|---|---|
e7d6055fa3 | |||
e0f3f116b9 | |||
352648b7cb | |||
2d5df25b1d | |||
cabe173831 | |||
d2c8a52ec6 | |||
bf53108384 | |||
77aac5534b | |||
58439fcd56 | |||
311a15363a | |||
255d3c8b39 | |||
41899c5831 | |||
fe4ca66ff6 | |||
0b91ba744a | |||
67483c2a90 | |||
af26e81bd3 | |||
186acbe915 |
@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/akamensky/argparse"
|
||||
"github.com/tim-beatham/wgmesh/pkg/ctrlserver"
|
||||
graph "github.com/tim-beatham/wgmesh/pkg/dot"
|
||||
"github.com/tim-beatham/wgmesh/pkg/ipc"
|
||||
logging "github.com/tim-beatham/wgmesh/pkg/log"
|
||||
)
|
||||
@ -91,17 +93,40 @@ func leaveMesh(client *ipcRpc.Client, meshId string) {
|
||||
fmt.Println(reply)
|
||||
}
|
||||
|
||||
func getGraph(client *ipcRpc.Client, meshId string) {
|
||||
var reply string
|
||||
func getGraph(client *ipcRpc.Client) {
|
||||
listMeshesReply := new(ipc.ListMeshReply)
|
||||
|
||||
err := client.Call("IpcHandler.GetDOT", &meshId, &reply)
|
||||
err := client.Call("IpcHandler.ListMeshes", "", &listMeshesReply)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(reply)
|
||||
meshes := make(map[string][]ctrlserver.MeshNode)
|
||||
|
||||
for _, meshId := range listMeshesReply.Meshes {
|
||||
var meshReply ipc.GetMeshReply
|
||||
|
||||
err := client.Call("IpcHandler.GetMesh", &meshId, &meshReply)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
meshes[meshId] = meshReply.Nodes
|
||||
}
|
||||
|
||||
dotGenerator := graph.NewMeshGraphConverter(meshes)
|
||||
dot, err := dotGenerator.Generate()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(dot)
|
||||
}
|
||||
|
||||
func queryMesh(client *ipcRpc.Client, meshId, query string) {
|
||||
@ -258,11 +283,6 @@ func main() {
|
||||
Help: "Advertise ::/0 into the mesh network",
|
||||
})
|
||||
|
||||
var getGraphMeshId *string = getGraphCmd.String("m", "mesh", &argparse.Options{
|
||||
Required: true,
|
||||
Help: "MeshID of the graph to get",
|
||||
})
|
||||
|
||||
var leaveMeshMeshId *string = leaveMeshCmd.String("m", "mesh", &argparse.Options{
|
||||
Required: true,
|
||||
Help: "MeshID of the mesh to leave",
|
||||
@ -351,7 +371,7 @@ func main() {
|
||||
}
|
||||
|
||||
if getGraphCmd.Happened() {
|
||||
getGraph(client, *getGraphMeshId)
|
||||
getGraph(client)
|
||||
}
|
||||
|
||||
if leaveMeshCmd.Happened() {
|
||||
|
@ -126,26 +126,6 @@ func ValidateDaemonConfiguration(c *DaemonConfiguration) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ParseMeshConfiguration: parses the mesh network configuration. Parses parameters such as
|
||||
// keepalive time, role and so forth.
|
||||
func ParseMeshConfiguration(filePath string) (*WgConfiguration, error) {
|
||||
var conf WgConfiguration
|
||||
|
||||
yamlBytes, err := os.ReadFile(filePath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(yamlBytes, &conf)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &conf, ValidateMeshConfiguration(&conf)
|
||||
}
|
||||
|
||||
// ParseDaemonConfiguration parses the mesh configuration and validates the configuration
|
||||
func ParseDaemonConfiguration(filePath string) (*DaemonConfiguration, error) {
|
||||
var conf DaemonConfiguration
|
||||
@ -162,6 +142,11 @@ func ParseDaemonConfiguration(filePath string) (*DaemonConfiguration, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.BaseConfiguration.KeepAliveWg == nil {
|
||||
var keepAlive int = 0
|
||||
conf.BaseConfiguration.KeepAliveWg = &keepAlive
|
||||
}
|
||||
|
||||
return &conf, ValidateDaemonConfiguration(&conf)
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,11 @@ type ConnectionManager interface {
|
||||
// If the endpoint does not exist then add the connection. Returns an error
|
||||
// if something went wrong
|
||||
GetConnection(endPoint string) (PeerConnection, error)
|
||||
// HasConnections returns true if a client has already registered at the givne
|
||||
// HasConnections returns true if a peer has already registered at the given
|
||||
// endpoint or false otherwise.
|
||||
HasConnection(endPoint string) bool
|
||||
// Removes a connection if it exists
|
||||
RemoveConnection(endPoint string) error
|
||||
// Goes through all the connections and closes eachone
|
||||
Close() error
|
||||
}
|
||||
@ -32,7 +34,6 @@ type ConnectionManagerImpl struct {
|
||||
// clientConnections maps an endpoint to a connection
|
||||
conLoc sync.RWMutex
|
||||
clientConnections map[string]PeerConnection
|
||||
serverConfig *tls.Config
|
||||
clientConfig *tls.Config
|
||||
connFactory PeerConnectionFactory
|
||||
}
|
||||
@ -61,37 +62,25 @@ func NewConnectionManager(params *NewConnectionManagerParams) (ConnectionManager
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverAuth := tls.RequireAndVerifyClientCert
|
||||
|
||||
if params.SkipCertVerification {
|
||||
serverAuth = tls.RequireAnyClientCert
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
|
||||
if !params.SkipCertVerification {
|
||||
|
||||
if params.CaCert == "" {
|
||||
return nil, errors.New("CA Cert is not specified")
|
||||
}
|
||||
|
||||
caCert, err := os.ReadFile(params.CaCert)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPool.AppendCertsFromPEM(caCert)
|
||||
if params.CaCert == "" {
|
||||
return nil, errors.New("CA Cert is not specified")
|
||||
}
|
||||
|
||||
serverConfig := &tls.Config{
|
||||
ClientAuth: serverAuth,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
caCert, err := os.ReadFile(params.CaCert)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok := certPool.AppendCertsFromPEM(caCert); !ok {
|
||||
return nil, errors.New("could not parse PEM")
|
||||
}
|
||||
|
||||
clientConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: params.SkipCertVerification,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: certPool,
|
||||
}
|
||||
|
||||
@ -99,7 +88,6 @@ func NewConnectionManager(params *NewConnectionManagerParams) (ConnectionManager
|
||||
connMgr := ConnectionManagerImpl{
|
||||
sync.RWMutex{},
|
||||
connections,
|
||||
serverConfig,
|
||||
clientConfig,
|
||||
params.ConnFactory,
|
||||
}
|
||||
@ -150,6 +138,15 @@ func (m *ConnectionManagerImpl) HasConnection(endPoint string) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
// RemoveConnection removes the given connection if it exists
|
||||
func (m *ConnectionManagerImpl) RemoveConnection(endPoint string) error {
|
||||
m.conLoc.Lock()
|
||||
err := m.clientConnections[endPoint].Close()
|
||||
|
||||
delete(m.clientConnections, endPoint)
|
||||
m.conLoc.Unlock()
|
||||
return err
|
||||
}
|
||||
func (m *ConnectionManagerImpl) Close() error {
|
||||
for _, conn := range m.clientConnections {
|
||||
if err := conn.Close(); err != nil {
|
||||
|
@ -53,13 +53,13 @@ func TestNewConnectionManagerCACertDoesNotExistAndVerify(t *testing.T) {
|
||||
|
||||
func TestNewConnectionManagerCACertDoesNotExistAndNotVerify(t *testing.T) {
|
||||
params := getConnectionManagerParams()
|
||||
params.CaCert = ""
|
||||
params.CaCert = "./cert/sdjsdjsdjk.pem"
|
||||
params.SkipCertVerification = true
|
||||
|
||||
_, err := NewConnectionManager(params)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(`an error should not be thrown`)
|
||||
if err == nil {
|
||||
t.Fatalf(`an error should be thrown`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,11 @@ package conn
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/tim-beatham/wgmesh/pkg/conf"
|
||||
logging "github.com/tim-beatham/wgmesh/pkg/log"
|
||||
@ -14,10 +17,8 @@ import (
|
||||
|
||||
// ConnectionServer manages gRPC server peer connections
|
||||
type ConnectionServer struct {
|
||||
// tlsConfiguration of the server
|
||||
serverConfig *tls.Config
|
||||
// server an instance of the grpc server
|
||||
server *grpc.Server // the authentication service to authenticate nodes
|
||||
server *grpc.Server
|
||||
// the ctrl service to manage node
|
||||
ctrlProvider rpc.MeshCtrlServerServer
|
||||
// the sync service to synchronise nodes
|
||||
@ -48,9 +49,26 @@ func NewConnectionServer(params *NewConnectionServerParams) (*ConnectionServer,
|
||||
serverAuth = tls.RequireAnyClientCert
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
|
||||
if params.Conf.CaCertificatePath == "" {
|
||||
return nil, errors.New("CA Cert is not specified")
|
||||
}
|
||||
|
||||
caCert, err := os.ReadFile(params.Conf.CaCertificatePath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok := certPool.AppendCertsFromPEM(caCert); !ok {
|
||||
return nil, errors.New("could not parse PEM")
|
||||
}
|
||||
|
||||
serverConfig := &tls.Config{
|
||||
ClientAuth: serverAuth,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientCAs: certPool,
|
||||
}
|
||||
|
||||
server := grpc.NewServer(
|
||||
@ -61,7 +79,6 @@ func NewConnectionServer(params *NewConnectionServerParams) (*ConnectionServer,
|
||||
syncProvider := params.SyncProvider
|
||||
|
||||
connServer := ConnectionServer{
|
||||
serverConfig: serverConfig,
|
||||
server: server,
|
||||
ctrlProvider: ctrlProvider,
|
||||
syncProvider: syncProvider,
|
||||
@ -74,7 +91,6 @@ func NewConnectionServer(params *NewConnectionServerParams) (*ConnectionServer,
|
||||
// Listen for incoming requests. Returns an error if something went wrong.
|
||||
func (s *ConnectionServer) Listen() error {
|
||||
rpc.RegisterMeshCtrlServerServer(s.server, s.ctrlProvider)
|
||||
|
||||
rpc.RegisterSyncServiceServer(s.server, s.syncProvider)
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Conf.GrpcPort))
|
||||
|
@ -16,6 +16,11 @@ func (s *ConnectionManagerStub) AddConnection(endPoint string) (PeerConnection,
|
||||
return mock, nil
|
||||
}
|
||||
|
||||
func (s *ConnectionManagerStub) RemoveConnection(endPoint string) error {
|
||||
delete(s.Endpoints, endPoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConnectionManagerStub) GetConnection(endPoint string) (PeerConnection, error) {
|
||||
endpoint, ok := s.Endpoints[endPoint]
|
||||
|
||||
|
@ -158,8 +158,8 @@ type TwoPhaseStoreMeshManager struct {
|
||||
IfName string
|
||||
Client *wgctrl.Client
|
||||
LastClock uint64
|
||||
conf *conf.WgConfiguration
|
||||
daemonConf *conf.DaemonConfiguration
|
||||
Conf *conf.WgConfiguration
|
||||
DaemonConf *conf.DaemonConfiguration
|
||||
store *TwoPhaseMap[string, MeshNode]
|
||||
}
|
||||
|
||||
@ -204,7 +204,6 @@ func (m *TwoPhaseStoreMeshManager) Save() []byte {
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
|
||||
err := enc.Encode(*snapshot)
|
||||
|
||||
if err != nil {
|
||||
@ -265,7 +264,7 @@ func (m *TwoPhaseStoreMeshManager) UpdateTimeStamp(nodeId string) error {
|
||||
|
||||
peerToUpdate := peers[0]
|
||||
|
||||
if uint64(time.Now().Unix())-m.store.Clock.GetTimestamp(peerToUpdate) > 3*uint64(m.daemonConf.KeepAliveTime) {
|
||||
if uint64(time.Now().Unix())-m.store.Clock.GetTimestamp(peerToUpdate) > 3*uint64(m.DaemonConf.KeepAliveTime) {
|
||||
m.store.Mark(peerToUpdate)
|
||||
|
||||
if len(peers) < 2 {
|
||||
@ -411,6 +410,11 @@ func (m *TwoPhaseStoreMeshManager) RemoveService(nodeId string, key string) erro
|
||||
}
|
||||
|
||||
node := m.store.Get(nodeId)
|
||||
|
||||
if _, ok := node.Services[key]; !ok {
|
||||
return fmt.Errorf("datastore: node does not contain service %s", key)
|
||||
}
|
||||
|
||||
delete(node.Services, key)
|
||||
m.store.Put(nodeId, node)
|
||||
return nil
|
||||
@ -510,5 +514,5 @@ func (m *TwoPhaseStoreMeshManager) RemoveNode(nodeId string) error {
|
||||
|
||||
// GetConfiguration implements mesh.MeshProvider.
|
||||
func (m *TwoPhaseStoreMeshManager) GetConfiguration() *conf.WgConfiguration {
|
||||
return m.conf
|
||||
return m.Conf
|
||||
}
|
||||
|
442
pkg/crdt/datastore_test.go
Normal file
442
pkg/crdt/datastore_test.go
Normal file
@ -0,0 +1,442 @@
|
||||
package crdt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tim-beatham/wgmesh/pkg/conf"
|
||||
"github.com/tim-beatham/wgmesh/pkg/lib"
|
||||
"github.com/tim-beatham/wgmesh/pkg/mesh"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
type TestParams struct {
|
||||
manager mesh.MeshProvider
|
||||
publicKey *wgtypes.Key
|
||||
}
|
||||
|
||||
func setUpTests() *TestParams {
|
||||
advertiseRoutes := false
|
||||
advertiseDefaultRoute := false
|
||||
role := conf.PEER_ROLE
|
||||
discovery := conf.DNS_IP_DISCOVERY
|
||||
|
||||
factory := &TwoPhaseMapFactory{
|
||||
Config: &conf.DaemonConfiguration{
|
||||
CertificatePath: "/somecertificatepath",
|
||||
PrivateKeyPath: "/someprivatekeypath",
|
||||
CaCertificatePath: "/somecacertificatepath",
|
||||
SkipCertVerification: true,
|
||||
GrpcPort: 0,
|
||||
Timeout: 20,
|
||||
Profile: false,
|
||||
SyncRate: 2,
|
||||
KeepAliveTime: 10,
|
||||
ClusterSize: 32,
|
||||
InterClusterChance: 0.15,
|
||||
BranchRate: 3,
|
||||
InfectionCount: 3,
|
||||
BaseConfiguration: conf.WgConfiguration{
|
||||
IPDiscovery: &discovery,
|
||||
AdvertiseRoutes: &advertiseRoutes,
|
||||
AdvertiseDefaultRoute: &advertiseDefaultRoute,
|
||||
Role: &role,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
key, _ := wgtypes.GeneratePrivateKey()
|
||||
|
||||
mesh, _ := factory.CreateMesh(&mesh.MeshProviderFactoryParams{
|
||||
DevName: "bob",
|
||||
MeshId: "meshid123",
|
||||
Client: nil,
|
||||
Conf: &factory.Config.BaseConfiguration,
|
||||
DaemonConf: factory.Config,
|
||||
NodeID: "bob",
|
||||
})
|
||||
|
||||
publicKey := key.PublicKey()
|
||||
|
||||
return &TestParams{
|
||||
manager: mesh,
|
||||
publicKey: &publicKey,
|
||||
}
|
||||
}
|
||||
|
||||
func getOurNode(testParams *TestParams) *MeshNode {
|
||||
return &MeshNode{
|
||||
HostEndpoint: "public-endpoint:8080",
|
||||
WgEndpoint: "public-endpoint:21906",
|
||||
WgHost: "3e9a:1fb3:5e50:8173:9690:f917:b1ab:d218/128",
|
||||
PublicKey: testParams.publicKey.String(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
Description: "A node that we are adding",
|
||||
Type: "peer",
|
||||
}
|
||||
}
|
||||
|
||||
func getRandomNode() *MeshNode {
|
||||
key, _ := wgtypes.GeneratePrivateKey()
|
||||
publicKey := key.PublicKey()
|
||||
|
||||
return &MeshNode{
|
||||
HostEndpoint: "public-endpoint:8081",
|
||||
WgEndpoint: "public-endpoint:21907",
|
||||
WgHost: "3e9a:1fb3:5e50:8173:9690:f917:b1ab:d234/128",
|
||||
PublicKey: publicKey.String(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
Description: "A node that we are adding",
|
||||
Type: "peer",
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddNodeAddsTheNodesToTheStore(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
|
||||
if !testParams.manager.NodeExists(testParams.publicKey.String()) {
|
||||
t.Fatalf(`node %s should have been added to the mesh network`, testParams.publicKey.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddNodeNodeAlreadyExistsReplacesTheNode(t *testing.T) {
|
||||
TestAddNodeAddsTheNodesToTheStore(t)
|
||||
TestAddNodeAddsTheNodesToTheStore(t)
|
||||
}
|
||||
|
||||
func TestSaveThenLoad(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
|
||||
bytes := testParams.manager.Save()
|
||||
|
||||
if err := testParams.manager.Load(bytes); err != nil {
|
||||
t.Fatalf(`error caused by loading datastore: %s`, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasChangesReturnsTrueWhenThereAreChangesInTheMesh(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
|
||||
if !testParams.manager.HasChanges() {
|
||||
t.Fatalf(`mesh has change but HasChanges returned false`)
|
||||
}
|
||||
|
||||
testParams.manager.SetDescription(testParams.publicKey.String(), "Bob marley")
|
||||
|
||||
if !testParams.manager.HasChanges() {
|
||||
t.Fatalf(`mesh has change but HasChanges returned false`)
|
||||
}
|
||||
|
||||
testParams.manager.SaveChanges()
|
||||
}
|
||||
|
||||
func TestHasChangesWhenThereAreNoChangesInTheMeshReturnsFalse(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
testParams.manager.AddNode(getRandomNode())
|
||||
|
||||
testParams.manager.SaveChanges()
|
||||
|
||||
if testParams.manager.HasChanges() {
|
||||
t.Fatalf(`mesh has no changes but HasChanges was true`)
|
||||
}
|
||||
|
||||
testParams.manager.SetDescription(testParams.publicKey.String(), "Bob marley")
|
||||
|
||||
testParams.manager.SaveChanges()
|
||||
|
||||
if testParams.manager.HasChanges() {
|
||||
t.Fatalf(`mesh has no changes but HasChanges was true`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTimeStampUpdatesTheTimeStampOfTheGivenNodeIfItIsTheLeader(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
|
||||
before, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
testParams.manager.UpdateTimeStamp(testParams.publicKey.String())
|
||||
|
||||
after, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
if before.GetTimeStamp() >= after.GetTimeStamp() {
|
||||
t.Fatalf(`before should not be after after`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTimeStampUpdatesTheTimeStampOfTheGivenNodeIfItIsNotLeader(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
|
||||
newNode := getRandomNode()
|
||||
newNode.PublicKey = "aaaaaaaaaa"
|
||||
|
||||
testParams.manager.AddNode(newNode)
|
||||
|
||||
before, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
after, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
if before.GetTimeStamp() != after.GetTimeStamp() {
|
||||
t.Fatalf(`before and after should be the same`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRoutesAddsARouteToTheGivenMesh(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
|
||||
_, destination, _ := net.ParseCIDR("0353:1da7:7f33:acc0:7a3f:6e55:912b:bc1f/64")
|
||||
|
||||
testParams.manager.AddRoutes(testParams.publicKey.String(), &mesh.RouteStub{
|
||||
Destination: destination,
|
||||
HopCount: 0,
|
||||
Path: make([]string, 0),
|
||||
})
|
||||
|
||||
node, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
containsDestination := lib.Contains(node.GetRoutes(), func(r mesh.Route) bool {
|
||||
return r.GetDestination().Contains(destination.IP)
|
||||
})
|
||||
|
||||
if !containsDestination {
|
||||
t.Fatalf(`route has not been added to the node`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRoutesWithdrawsRoutesFromTheMesh(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
|
||||
_, destination, _ := net.ParseCIDR("0353:1da7:7f33:acc0:7a3f:6e55:912b:bc1f/64")
|
||||
route := &mesh.RouteStub{
|
||||
Destination: destination,
|
||||
HopCount: 0,
|
||||
Path: make([]string, 0),
|
||||
}
|
||||
|
||||
testParams.manager.AddRoutes(testParams.publicKey.String(), route)
|
||||
testParams.manager.RemoveRoutes(testParams.publicKey.String(), route)
|
||||
|
||||
node, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
containsDestination := lib.Contains(node.GetRoutes(), func(r mesh.Route) bool {
|
||||
return r.GetDestination().Contains(destination.IP)
|
||||
})
|
||||
|
||||
if containsDestination {
|
||||
t.Fatalf(`route has not been removed from the node`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNodeGetsTheNodeWhenItExists(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
node, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
if node == nil {
|
||||
t.Fatalf(`node not found returned nil`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNodeReturnsNilWhenItDoesNotExist(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
testParams.manager.RemoveNode(testParams.publicKey.String())
|
||||
|
||||
node, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
if node != nil {
|
||||
t.Fatalf(`node found but should be nil`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeExistsReturnsFalseWhenNotExists(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
testParams.manager.RemoveNode(testParams.publicKey.String())
|
||||
|
||||
if testParams.manager.NodeExists(testParams.publicKey.String()) {
|
||||
t.Fatalf(`nodeexists should be false`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDescriptionReturnsErrorWhenNodeDoesNotExist(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
err := testParams.manager.SetDescription("djdjdj", "djdsjkd")
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf(`error should be thrown`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDescriptionSetsTheDescription(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
descriptionToSet := "djdsjkd"
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
err := testParams.manager.SetDescription(testParams.publicKey.String(), descriptionToSet)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf(`error %s thrown`, err.Error())
|
||||
}
|
||||
|
||||
node, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
description := node.GetDescription()
|
||||
|
||||
if description != descriptionToSet {
|
||||
t.Fatalf(`description was %s should be %s`, description, descriptionToSet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliasNodeDoesNotExist(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
err := testParams.manager.SetAlias("djdjdj", "djdsjkd")
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf(`error should be thrown`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAliasSetsAlias(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
aliasToSet := "djdsjkd"
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
err := testParams.manager.SetAlias(testParams.publicKey.String(), aliasToSet)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf(`error %s thrown`, err.Error())
|
||||
}
|
||||
|
||||
node, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
alias := node.GetAlias()
|
||||
|
||||
if alias != aliasToSet {
|
||||
t.Fatalf(`description was %s should be %s`, alias, aliasToSet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddServiceNodeDoesNotExist(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
err := testParams.manager.AddService("djdjdj", "djdsjkd", "sddsds")
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf(`error should be thrown`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddServiceNodeExists(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
service := "djdsjkd"
|
||||
serviceValue := "dsdsds"
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
err := testParams.manager.AddService(testParams.publicKey.String(), service, serviceValue)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf(`error %s thrown`, err.Error())
|
||||
}
|
||||
|
||||
node, _ := testParams.manager.GetNode(testParams.publicKey.String())
|
||||
|
||||
services := node.GetServices()
|
||||
|
||||
if value, ok := services[service]; !ok || value != serviceValue {
|
||||
t.Fatalf(`service not added to the data store`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveServiceDoesNotExists(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
err := testParams.manager.RemoveService("djdjdj", "dsdssd")
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf(`error should be thrown`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveServiceServiceDoesNotExist(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
testParams.manager.AddNode(getOurNode(testParams))
|
||||
|
||||
if err := testParams.manager.RemoveService(testParams.publicKey.String(), "dhsdh"); err == nil {
|
||||
t.Fatalf(`error should be thrown`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPeersReturnsAllPeersInTheMesh(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
peer1 := getRandomNode()
|
||||
peer2 := getRandomNode()
|
||||
client := getRandomNode()
|
||||
client.Type = "client"
|
||||
|
||||
testParams.manager.AddNode(peer1)
|
||||
testParams.manager.AddNode(peer2)
|
||||
testParams.manager.AddNode(client)
|
||||
|
||||
peers := testParams.manager.GetPeers()
|
||||
slices.Sort(peers)
|
||||
|
||||
if len(peers) != 2 {
|
||||
t.Fatalf(`there should be two peers in the mesh`)
|
||||
}
|
||||
|
||||
peer1Pub, _ := peer1.GetPublicKey()
|
||||
|
||||
if !slices.Contains(peers, peer1Pub.String()) {
|
||||
t.Fatalf(`peer1 not in the list`)
|
||||
}
|
||||
|
||||
peer2Pub, _ := peer2.GetPublicKey()
|
||||
|
||||
if !slices.Contains(peers, peer2Pub.String()) {
|
||||
t.Fatalf(`peer2 not in the list`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveNodeReturnsErrorIfNodeDoesNotExist(t *testing.T) {
|
||||
testParams := setUpTests()
|
||||
|
||||
err := testParams.manager.RemoveNode("dsjdssjk")
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf(`error should have returned`)
|
||||
}
|
||||
}
|
@ -18,8 +18,8 @@ func (f *TwoPhaseMapFactory) CreateMesh(params *mesh.MeshProviderFactoryParams)
|
||||
MeshId: params.MeshId,
|
||||
IfName: params.DevName,
|
||||
Client: params.Client,
|
||||
conf: params.Conf,
|
||||
daemonConf: params.DaemonConf,
|
||||
Conf: params.Conf,
|
||||
DaemonConf: params.DaemonConf,
|
||||
store: NewTwoPhaseMap[string, MeshNode](params.NodeID, func(s string) uint64 {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(s))
|
||||
|
@ -1,4 +1,4 @@
|
||||
// crdt is a golang implementation of a crdt
|
||||
// crdt provides go implementations for crdts
|
||||
package crdt
|
||||
|
||||
import (
|
||||
@ -65,10 +65,19 @@ func (g *GMap[K, D]) get(key uint64) Bucket[D] {
|
||||
}
|
||||
|
||||
func (g *GMap[K, D]) Get(key K) D {
|
||||
if !g.Contains(key) {
|
||||
var def D
|
||||
return def
|
||||
}
|
||||
|
||||
return g.get(g.clock.hashFunc(key)).Contents
|
||||
}
|
||||
|
||||
func (g *GMap[K, D]) Mark(key K) {
|
||||
if !g.Contains(key) {
|
||||
return
|
||||
}
|
||||
|
||||
g.lock.Lock()
|
||||
bucket := g.contents[g.clock.hashFunc(key)]
|
||||
bucket.Gravestone = true
|
||||
@ -89,7 +98,6 @@ func (g *GMap[K, D]) IsMarked(key K) bool {
|
||||
}
|
||||
|
||||
g.lock.RUnlock()
|
||||
|
||||
return marked
|
||||
}
|
||||
|
||||
|
224
pkg/crdt/g_map_test.go
Normal file
224
pkg/crdt/g_map_test.go
Normal file
@ -0,0 +1,224 @@
|
||||
// crdt_test unit tests the crdt implementations
|
||||
package crdt
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tim-beatham/wgmesh/pkg/lib"
|
||||
)
|
||||
|
||||
func NewGmap() *GMap[string, bool] {
|
||||
vectorClock := NewVectorClock("a", func(key string) uint64 {
|
||||
hash := fnv.New64a()
|
||||
hash.Write([]byte(key))
|
||||
return hash.Sum64()
|
||||
}, 1) // 1 second stale time
|
||||
|
||||
gMap := NewGMap[string, bool](vectorClock)
|
||||
return gMap
|
||||
}
|
||||
|
||||
func TestGMapPutInsertsItems(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("bruh1234", true)
|
||||
|
||||
if !gMap.Contains("bruh1234") {
|
||||
t.Fatalf(`value not added to map`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGMapPutReplacesItems(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("bruh1234", true)
|
||||
gMap.Put("bruh1234", false)
|
||||
|
||||
value := gMap.Get("bruh1234")
|
||||
|
||||
if value {
|
||||
t.Fatalf(`value should ahve been replaced to false`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsValueNotPresent(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
|
||||
if gMap.Contains("sdhjsdhsdj") {
|
||||
t.Fatalf(`value should not be present in the map`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsValuePresent(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
key := "hehehehe"
|
||||
gMap.Put(key, false)
|
||||
|
||||
if !gMap.Contains(key) {
|
||||
t.Fatalf(`%s should not be present in the map`, key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGMapGetNotPresentReturnsError(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
value := gMap.Get("bruh123")
|
||||
|
||||
if value != false {
|
||||
t.Fatalf(`value should be default type false`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGMapGetReturnsValue(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("bobdylan", true)
|
||||
|
||||
value := gMap.Get("bobdylan")
|
||||
|
||||
if !value {
|
||||
t.Fatalf("value should be true but was false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkMarksTheValue(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("hello123", true)
|
||||
|
||||
gMap.Mark("hello123")
|
||||
|
||||
if !gMap.IsMarked("hello123") {
|
||||
t.Fatal(`hello123 should be marked`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkValueNotPresent(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Mark("ok123456")
|
||||
}
|
||||
|
||||
func TestKeysMapEmpty(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
|
||||
keys := gMap.Keys()
|
||||
|
||||
if len(keys) != 0 {
|
||||
t.Fatal(`list of keys was not empty but should be empty`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeysMapReturnsKeysInMap(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
|
||||
gMap.Put("a", false)
|
||||
gMap.Put("b", false)
|
||||
gMap.Put("c", false)
|
||||
|
||||
keys := gMap.Keys()
|
||||
|
||||
if len(keys) != 3 {
|
||||
t.Fatal(`key length should be 3`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveMapEmptyReturnsEmptyMap(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
|
||||
saveMap := gMap.Save()
|
||||
|
||||
if len(saveMap) != 0 {
|
||||
t.Fatal(`saves should be empty`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveMapReturnsMapOfBuckets(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("a", false)
|
||||
gMap.Put("b", false)
|
||||
gMap.Put("c", false)
|
||||
|
||||
saveMap := gMap.Save()
|
||||
|
||||
if len(saveMap) != 3 {
|
||||
t.Fatalf(`save length should be 3`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveWithKeysNoKeysReturnsEmptyBucket(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("a", false)
|
||||
gMap.Put("b", false)
|
||||
gMap.Put("c", false)
|
||||
|
||||
saveMap := gMap.SaveWithKeys([]uint64{})
|
||||
|
||||
if len(saveMap) != 0 {
|
||||
t.Fatalf(`save map should be empty`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveWithKeysReturnsIntersection(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("a", false)
|
||||
gMap.Put("b", false)
|
||||
gMap.Put("c", false)
|
||||
|
||||
clock := lib.MapKeys(gMap.GetClock())
|
||||
clock = clock[:len(clock)-1]
|
||||
|
||||
values := gMap.SaveWithKeys(clock)
|
||||
if len(values) != len(clock) {
|
||||
t.Fatalf(`intersection not returned`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClockMapEmptyReturnsEmptyClock(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
|
||||
clocks := gMap.GetClock()
|
||||
|
||||
if len(clocks) != 0 {
|
||||
t.Fatalf(`vector clock is not empty`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClockReturnsAllCLocks(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("a", false)
|
||||
gMap.Put("b", false)
|
||||
gMap.Put("c", false)
|
||||
|
||||
clocks := lib.MapValues(gMap.GetClock())
|
||||
slices.Sort(clocks)
|
||||
|
||||
if !slices.Equal([]uint64{0, 1, 2}, clocks) {
|
||||
t.Fatalf(`clocks are invalid`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHashChangesHashOnValueAdded(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.Put("a", false)
|
||||
prevHash := gMap.GetHash()
|
||||
|
||||
gMap.Put("b", true)
|
||||
|
||||
if prevHash == gMap.GetHash() {
|
||||
t.Fatalf(`hash should be different`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruneGarbageCollectsValuesThatHaveNotBeenUpdated(t *testing.T) {
|
||||
gMap := NewGmap()
|
||||
gMap.clock.Put("c", 12)
|
||||
gMap.Put("c", false)
|
||||
gMap.Put("a", false)
|
||||
|
||||
time.Sleep(4 * time.Second)
|
||||
gMap.Put("a", true)
|
||||
|
||||
gMap.Prune()
|
||||
|
||||
if gMap.Contains("c") {
|
||||
t.Fatalf(`a should have been pruned`)
|
||||
}
|
||||
}
|
214
pkg/crdt/two_phase_map_test.go
Normal file
214
pkg/crdt/two_phase_map_test.go
Normal file
@ -0,0 +1,214 @@
|
||||
package crdt
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func NewMap(processId string) *TwoPhaseMap[string, string] {
|
||||
theMap := NewTwoPhaseMap[string, string](processId, func(key string) uint64 {
|
||||
hash := fnv.New64a()
|
||||
hash.Write([]byte(key))
|
||||
return hash.Sum64()
|
||||
}, 1)
|
||||
return theMap
|
||||
}
|
||||
|
||||
func TestTwoPhaseMapEmpty(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
|
||||
if theMap.Contains("a") {
|
||||
t.Fatalf(`a should not be present in the map`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoPhaseMapValuePresent(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
theMap.Put("a", "")
|
||||
|
||||
if !theMap.Contains("a") {
|
||||
t.Fatalf(`should be present within the map`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoPhaseMapValueNotPresent(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
theMap.Put("b", "")
|
||||
|
||||
if theMap.Contains("a") {
|
||||
t.Fatalf(`a should not be present in the map`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoPhaseMapPutThenRemove(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
|
||||
theMap.Put("a", "")
|
||||
theMap.Remove("a")
|
||||
|
||||
if theMap.Contains("a") {
|
||||
t.Fatalf(`a should not be present within the map`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoPhaseMapPutThenRemoveThenPut(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
|
||||
theMap.Put("a", "")
|
||||
theMap.Remove("a")
|
||||
theMap.Put("a", "")
|
||||
|
||||
if !theMap.Contains("a") {
|
||||
t.Fatalf(`a should be present within the map`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkMarksTheValueIn2PMap(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
|
||||
theMap.Put("a", "")
|
||||
theMap.Mark("a")
|
||||
|
||||
if !theMap.IsMarked("a") {
|
||||
t.Fatalf(`a should be marked`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsListReturnsItemsInList(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
|
||||
theMap.Put("a", "bob")
|
||||
theMap.Put("b", "dylan")
|
||||
|
||||
keys := theMap.AsList()
|
||||
slices.Sort(keys)
|
||||
|
||||
if !slices.Equal([]string{"bob", "dylan"}, keys) {
|
||||
t.Fatalf(`values should be bob, dylan`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapShotRemoveMapEmpty(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
theMap.Put("a", "bob")
|
||||
theMap.Put("b", "dylan")
|
||||
|
||||
snapshot := theMap.Snapshot()
|
||||
|
||||
if len(snapshot.Add) != 2 {
|
||||
t.Fatalf(`add values length should be 2`)
|
||||
}
|
||||
|
||||
if len(snapshot.Remove) != 0 {
|
||||
t.Fatalf(`remove map length should be 0`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotMapEmpty(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
|
||||
snapshot := theMap.Snapshot()
|
||||
|
||||
if len(snapshot.Add) != 0 || len(snapshot.Remove) != 0 {
|
||||
t.Fatalf(`snapshot length should be 0`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapShotFromStateReturnsIntersection(t *testing.T) {
|
||||
map1 := NewMap("a")
|
||||
map1.Put("a", "heyy")
|
||||
|
||||
map2 := NewMap("b")
|
||||
map2.Put("b", "hmmm")
|
||||
|
||||
message := map2.GenerateMessage()
|
||||
|
||||
snapShot := map1.SnapShotFromState(message)
|
||||
|
||||
if len(snapShot.Add) != 1 {
|
||||
t.Fatalf(`add length should be 1`)
|
||||
}
|
||||
|
||||
if len(snapShot.Remove) != 0 {
|
||||
t.Fatalf(`remove length should be 0`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHashDifferentOnChange(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
|
||||
prevHash := theMap.GetHash()
|
||||
|
||||
theMap.Put("b", "hmmhmhmh")
|
||||
|
||||
if prevHash == theMap.GetHash() {
|
||||
t.Fatalf(`hashes should not be the same`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateMessageReturnsClocks(t *testing.T) {
|
||||
theMap := NewMap("a")
|
||||
theMap.Put("a", "hmm")
|
||||
theMap.Put("b", "hmm")
|
||||
theMap.Remove("a")
|
||||
|
||||
message := theMap.GenerateMessage()
|
||||
|
||||
if len(message.AddContents) != 2 {
|
||||
t.Fatalf(`two items added add should be 2`)
|
||||
}
|
||||
|
||||
if len(message.RemoveContents) != 1 {
|
||||
t.Fatalf(`a was removed remove map should be length 1`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifferenceReturnsDifferenceOfMaps(t *testing.T) {
|
||||
map1 := NewMap("a")
|
||||
map1.Put("a", "ssms")
|
||||
map1.Put("b", "sdmdsmd")
|
||||
|
||||
map2 := NewMap("b")
|
||||
map2.Put("d", "eek")
|
||||
map2.Put("c", "meh")
|
||||
|
||||
message1 := map1.GenerateMessage()
|
||||
message2 := map2.GenerateMessage()
|
||||
|
||||
difference := message1.Difference(0, message2)
|
||||
|
||||
if len(difference.AddContents) != 2 {
|
||||
t.Fatalf(`d and c are not in map1 they should be in add contents`)
|
||||
}
|
||||
|
||||
if len(difference.RemoveContents) != 0 {
|
||||
t.Fatalf(`remove should be empty`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeMergesValuesThatAreGreaterThanCurrentClock(t *testing.T) {
|
||||
map1 := NewMap("a")
|
||||
map1.Put("a", "ssms")
|
||||
map1.Put("b", "sdmdsmd")
|
||||
|
||||
map2 := NewMap("b")
|
||||
map2.Put("d", "eek")
|
||||
map2.Put("c", "meh")
|
||||
|
||||
message1 := map1.GenerateMessage()
|
||||
message2 := map2.GenerateMessage()
|
||||
|
||||
difference := message1.Difference(0, message2)
|
||||
state := map2.SnapShotFromState(difference)
|
||||
|
||||
map1.Merge(*state)
|
||||
|
||||
if !map1.Contains("d") {
|
||||
t.Fatalf(`d should be in the map`)
|
||||
}
|
||||
|
||||
if !map2.Contains("c") {
|
||||
t.Fatalf(`c should be in the map`)
|
||||
}
|
||||
}
|
227
pkg/dot/dot.go
Normal file
227
pkg/dot/dot.go
Normal file
@ -0,0 +1,227 @@
|
||||
// Graph allows the definition of a DOT graph in golang
|
||||
package graph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
|
||||
"github.com/tim-beatham/wgmesh/pkg/lib"
|
||||
)
|
||||
|
||||
type GraphType string
|
||||
type Shape string
|
||||
|
||||
const (
|
||||
GRAPH GraphType = "graph"
|
||||
DIGRAPH GraphType = "digraph"
|
||||
)
|
||||
|
||||
const (
|
||||
CIRCLE Shape = "circle"
|
||||
STAR Shape = "star"
|
||||
HEXAGON Shape = "hexagon"
|
||||
PARALLELOGRAM Shape = "parallelogram"
|
||||
)
|
||||
|
||||
type Graph interface {
|
||||
Dottable
|
||||
GetType() GraphType
|
||||
}
|
||||
|
||||
type Cluster struct {
|
||||
Type GraphType
|
||||
Name string
|
||||
Label string
|
||||
nodes map[string]*Node
|
||||
edges map[string]Edge
|
||||
}
|
||||
|
||||
type RootGraph struct {
|
||||
Type GraphType
|
||||
Label string
|
||||
nodes map[string]*Node
|
||||
clusters map[string]*Cluster
|
||||
edges map[string]Edge
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Name string
|
||||
Label string
|
||||
Shape Shape
|
||||
Size int
|
||||
}
|
||||
|
||||
type Edge interface {
|
||||
Dottable
|
||||
}
|
||||
|
||||
type DirectedEdge struct {
|
||||
Name string
|
||||
Label string
|
||||
From string
|
||||
To string
|
||||
}
|
||||
|
||||
type UndirectedEdge struct {
|
||||
Name string
|
||||
Label string
|
||||
From string
|
||||
To string
|
||||
}
|
||||
|
||||
// Dottable means an implementer can convert the struct to DOT representation
|
||||
type Dottable interface {
|
||||
GetDOT() (string, error)
|
||||
}
|
||||
|
||||
func NewGraph(label string, graphType GraphType) *RootGraph {
|
||||
return &RootGraph{Type: graphType, Label: label, clusters: map[string]*Cluster{}, nodes: make(map[string]*Node), edges: make(map[string]Edge)}
|
||||
}
|
||||
|
||||
// PutNode: puts a node in the graph
|
||||
func (g *RootGraph) PutNode(name, label string, size int, shape Shape) error {
|
||||
_, exists := g.nodes[name]
|
||||
|
||||
if exists {
|
||||
// If exists no need to add the ndoe
|
||||
return nil
|
||||
}
|
||||
|
||||
g.nodes[name] = &Node{Name: name, Label: label, Size: size, Shape: shape}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *RootGraph) PutCluster(graph *Cluster) {
|
||||
g.clusters[graph.Label] = graph
|
||||
}
|
||||
|
||||
func writeContituents[D Dottable](result *strings.Builder, elements ...D) error {
|
||||
for _, node := range elements {
|
||||
dot, err := node.GetDOT()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = result.WriteString(dot)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *RootGraph) GetDOT() (string, error) {
|
||||
var result strings.Builder
|
||||
|
||||
result.WriteString(fmt.Sprintf("%s {\n", g.Type))
|
||||
result.WriteString("node [colorscheme=set312];\n")
|
||||
result.WriteString("layout = fdp;\n")
|
||||
nodes := lib.MapValues(g.nodes)
|
||||
edges := lib.MapValues(g.edges)
|
||||
writeContituents(&result, nodes...)
|
||||
writeContituents(&result, edges...)
|
||||
|
||||
for _, cluster := range g.clusters {
|
||||
clusterDOT, err := cluster.GetDOT()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result.WriteString(clusterDOT)
|
||||
}
|
||||
|
||||
result.WriteString("}")
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
// GetType implements Graph.
|
||||
func (r *RootGraph) GetType() GraphType {
|
||||
return r.Type
|
||||
}
|
||||
|
||||
func constructEdge(graph Graph, name, label, from, to string) Edge {
|
||||
switch graph.GetType() {
|
||||
case DIGRAPH:
|
||||
return &DirectedEdge{Name: name, Label: label, From: from, To: to}
|
||||
default:
|
||||
return &UndirectedEdge{Name: name, Label: label, From: from, To: to}
|
||||
}
|
||||
}
|
||||
|
||||
// AddEdge: adds an edge between two nodes in the graph
|
||||
func (g *RootGraph) AddEdge(name string, label string, from string, to string) error {
|
||||
g.edges[name] = constructEdge(g, name, label, from, to)
|
||||
return nil
|
||||
}
|
||||
|
||||
const numColours = 12
|
||||
|
||||
func (n *Node) hash() int {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(n.Name))
|
||||
return (int(h.Sum32()) % numColours) + 1
|
||||
}
|
||||
|
||||
func (n *Node) GetDOT() (string, error) {
|
||||
return fmt.Sprintf("node[label=\"%s\",shape=%s, style=\"filled\", fillcolor=%d, width=%d, height=%d, fixedsize=true] \"%s\";\n",
|
||||
n.Label, n.Shape, n.hash(), n.Size, n.Size, n.Name), nil
|
||||
}
|
||||
|
||||
func (e *DirectedEdge) GetDOT() (string, error) {
|
||||
return fmt.Sprintf("\"%s\" -> \"%s\" [label=\"%s\"];\n", e.From, e.To, e.Label), nil
|
||||
}
|
||||
|
||||
func (e *UndirectedEdge) GetDOT() (string, error) {
|
||||
return fmt.Sprintf("\"%s\" -- \"%s\" [label=\"%s\"];\n", e.From, e.To, e.Label), nil
|
||||
}
|
||||
|
||||
// AddEdge: adds an edge between two nodes in the graph
|
||||
func (g *Cluster) AddEdge(name string, label string, from string, to string) error {
|
||||
g.edges[name] = constructEdge(g, name, label, from, to)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutNode: puts a node in the graph
|
||||
func (g *Cluster) PutNode(name, label string, size int, shape Shape) error {
|
||||
_, exists := g.nodes[name]
|
||||
|
||||
if exists {
|
||||
// If exists no need to add the ndoe
|
||||
return nil
|
||||
}
|
||||
|
||||
g.nodes[name] = &Node{Name: name, Label: label, Shape: shape, Size: size}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Cluster) GetDOT() (string, error) {
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString(fmt.Sprintf("subgraph \"cluster%s\" {\n", g.Label))
|
||||
builder.WriteString(fmt.Sprintf("label = \"%s\"\n", g.Label))
|
||||
nodes := lib.MapValues(g.nodes)
|
||||
edges := lib.MapValues(g.edges)
|
||||
writeContituents(&builder, nodes...)
|
||||
writeContituents(&builder, edges...)
|
||||
|
||||
builder.WriteString("}\n")
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func (g *Cluster) GetType() GraphType {
|
||||
return g.Type
|
||||
}
|
||||
|
||||
func NewSubGraph(name string, label string, graphType GraphType) *Cluster {
|
||||
return &Cluster{
|
||||
Label: name,
|
||||
Type: graphType,
|
||||
Name: name,
|
||||
nodes: make(map[string]*Node),
|
||||
edges: make(map[string]Edge),
|
||||
}
|
||||
}
|
116
pkg/dot/wg.go
Normal file
116
pkg/dot/wg.go
Normal file
@ -0,0 +1,116 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/tim-beatham/wgmesh/pkg/ctrlserver"
|
||||
)
|
||||
|
||||
// MeshGraphConverter converts a mesh to a graph
|
||||
type MeshGraphConverter interface {
|
||||
// convert the mesh to textual form
|
||||
Generate() (string, error)
|
||||
}
|
||||
|
||||
type MeshDOTConverter struct {
|
||||
meshes map[string][]ctrlserver.MeshNode
|
||||
destinations map[string]interface{}
|
||||
}
|
||||
|
||||
func (c *MeshDOTConverter) Generate() (string, error) {
|
||||
g := NewGraph("Smegmesh", GRAPH)
|
||||
|
||||
for meshId := range c.meshes {
|
||||
err := c.generateMesh(g, meshId)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
for mesh := range c.meshes {
|
||||
g.PutNode(mesh, mesh, 1, CIRCLE)
|
||||
}
|
||||
|
||||
for destination := range c.destinations {
|
||||
g.PutNode(destination, destination, 1, HEXAGON)
|
||||
}
|
||||
|
||||
return g.GetDOT()
|
||||
}
|
||||
|
||||
func (c *MeshDOTConverter) generateMesh(g *RootGraph, meshId string) error {
|
||||
nodes := c.meshes[meshId]
|
||||
|
||||
g.PutNode(meshId, meshId, 1, CIRCLE)
|
||||
|
||||
for _, node := range nodes {
|
||||
c.graphNode(g, node, meshId)
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
g.AddEdge(fmt.Sprintf("%s to %s", node.PublicKey, meshId), "", node.PublicKey, meshId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// graphNode: graphs a node within the mesh
|
||||
func (c *MeshDOTConverter) graphNode(g *RootGraph, node ctrlserver.MeshNode, meshId string) {
|
||||
alias := node.Alias
|
||||
|
||||
if alias == "" {
|
||||
alias = node.WgHost[1:len(node.WgHost)-20] + "\\n" + node.WgHost[len(node.WgHost)-20:len(node.WgHost)]
|
||||
}
|
||||
|
||||
g.PutNode(node.PublicKey, alias, 2, CIRCLE)
|
||||
|
||||
for _, route := range node.Routes {
|
||||
if len(route.Path) == 0 {
|
||||
g.AddEdge(route.Destination, "", node.PublicKey, route.Destination)
|
||||
continue
|
||||
}
|
||||
|
||||
reversedPath := slices.Clone(route.Path)
|
||||
slices.Reverse(reversedPath)
|
||||
|
||||
g.AddEdge(fmt.Sprintf("%s to %s", node.PublicKey, reversedPath[0]), "", node.PublicKey, reversedPath[0])
|
||||
|
||||
for _, mesh := range route.Path {
|
||||
if _, ok := c.meshes[mesh]; !ok {
|
||||
c.destinations[mesh] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for index := range reversedPath[0 : len(reversedPath)-1] {
|
||||
routeID := fmt.Sprintf("%s to %s", reversedPath[index], reversedPath[index+1])
|
||||
g.AddEdge(routeID, "", reversedPath[index], reversedPath[index+1])
|
||||
}
|
||||
|
||||
if route.Destination == "::/0" {
|
||||
c.destinations[route.Destination] = struct{}{}
|
||||
lastMesh := reversedPath[len(reversedPath)-1]
|
||||
routeID := fmt.Sprintf("%s to %s", lastMesh, route.Destination)
|
||||
g.AddEdge(routeID, "", lastMesh, route.Destination)
|
||||
}
|
||||
}
|
||||
|
||||
for service := range node.Services {
|
||||
c.putService(g, service, meshId, node)
|
||||
}
|
||||
}
|
||||
|
||||
// putService: construct a service node and a link between the nodes
|
||||
func (c *MeshDOTConverter) putService(g *RootGraph, key, meshId string, node ctrlserver.MeshNode) {
|
||||
serviceID := fmt.Sprintf("%s%s%s", key, node.PublicKey, meshId)
|
||||
g.PutNode(serviceID, key, 1, PARALLELOGRAM)
|
||||
g.AddEdge(fmt.Sprintf("%s to %s", node.PublicKey, serviceID), "", node.PublicKey, serviceID)
|
||||
}
|
||||
|
||||
func NewMeshGraphConverter(meshes map[string][]ctrlserver.MeshNode) MeshGraphConverter {
|
||||
return &MeshDOTConverter{
|
||||
meshes: meshes,
|
||||
destinations: make(map[string]interface{}),
|
||||
}
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
// Graph allows the definition of a DOT graph in golang
|
||||
package graph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
|
||||
"github.com/tim-beatham/wgmesh/pkg/lib"
|
||||
)
|
||||
|
||||
type GraphType string
|
||||
type Shape string
|
||||
|
||||
const (
|
||||
GRAPH GraphType = "graph"
|
||||
DIGRAPH = "digraph"
|
||||
)
|
||||
|
||||
const (
|
||||
CIRCLE Shape = "circle"
|
||||
STAR Shape = "star"
|
||||
HEXAGON Shape = "hexagon"
|
||||
)
|
||||
|
||||
type Graph struct {
|
||||
Type GraphType
|
||||
Label string
|
||||
nodes map[string]*Node
|
||||
edges []Edge
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Name string
|
||||
Shape Shape
|
||||
}
|
||||
|
||||
type Edge interface {
|
||||
Dottable
|
||||
}
|
||||
|
||||
type DirectedEdge struct {
|
||||
Label string
|
||||
From *Node
|
||||
To *Node
|
||||
}
|
||||
|
||||
type UndirectedEdge struct {
|
||||
Label string
|
||||
From *Node
|
||||
To *Node
|
||||
}
|
||||
|
||||
// Dottable means an implementer can convert the struct to DOT representation
|
||||
type Dottable interface {
|
||||
GetDOT() (string, error)
|
||||
}
|
||||
|
||||
func NewGraph(label string, graphType GraphType) *Graph {
|
||||
return &Graph{Type: graphType, Label: label, nodes: make(map[string]*Node), edges: make([]Edge, 0)}
|
||||
}
|
||||
|
||||
// PutNode: puts a node in the graph
|
||||
func (g *Graph) PutNode(label string, shape Shape) error {
|
||||
_, exists := g.nodes[label]
|
||||
|
||||
if exists {
|
||||
// If exists no need to add the ndoe
|
||||
return nil
|
||||
}
|
||||
|
||||
g.nodes[label] = &Node{Name: label, Shape: shape}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeContituents[D Dottable](result *strings.Builder, elements ...D) error {
|
||||
for _, node := range elements {
|
||||
dot, err := node.GetDOT()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = result.WriteString(dot)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graph) GetDOT() (string, error) {
|
||||
var result strings.Builder
|
||||
|
||||
_, err := result.WriteString(fmt.Sprintf("%s {\n", g.Type))
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = result.WriteString("node [colorscheme=set312];\n")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nodes := lib.MapValues(g.nodes)
|
||||
|
||||
err = writeContituents(&result, nodes...)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = writeContituents(&result, g.edges...)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = result.WriteString("}")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
func (g *Graph) constructEdge(label string, from *Node, to *Node) Edge {
|
||||
switch g.Type {
|
||||
case DIGRAPH:
|
||||
return &DirectedEdge{Label: label, From: from, To: to}
|
||||
default:
|
||||
return &UndirectedEdge{Label: label, From: from, To: to}
|
||||
}
|
||||
}
|
||||
|
||||
// AddEdge: adds an edge between two nodes in the graph
|
||||
func (g *Graph) AddEdge(label string, from string, to string) error {
|
||||
fromNode, exists := g.nodes[from]
|
||||
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Node %s does not exist", from))
|
||||
}
|
||||
|
||||
toNode, exists := g.nodes[to]
|
||||
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Node %s does not exist", to))
|
||||
}
|
||||
|
||||
g.edges = append(g.edges, g.constructEdge(label, fromNode, toNode))
|
||||
return nil
|
||||
}
|
||||
|
||||
const numColours = 12
|
||||
|
||||
func (n *Node) hash() int {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(n.Name))
|
||||
return (int(h.Sum32()) % numColours) + 1
|
||||
}
|
||||
|
||||
func (n *Node) GetDOT() (string, error) {
|
||||
return fmt.Sprintf("node[shape=%s, style=\"filled\", fillcolor=%d] %s;\n",
|
||||
n.Shape, n.hash(), n.Name), nil
|
||||
}
|
||||
|
||||
func (e *DirectedEdge) GetDOT() (string, error) {
|
||||
return fmt.Sprintf("%s -> %s;\n", e.From.Name, e.To.Name), nil
|
||||
}
|
||||
|
||||
func (e *UndirectedEdge) GetDOT() (string, error) {
|
||||
return fmt.Sprintf("%s -- %s;\n", e.From.Name, e.To.Name), nil
|
||||
}
|
@ -68,7 +68,6 @@ type MeshIpc interface {
|
||||
JoinMesh(args JoinMeshArgs, reply *string) error
|
||||
LeaveMesh(meshId string, reply *string) error
|
||||
GetMesh(meshId string, reply *GetMeshReply) error
|
||||
GetDOT(meshId string, reply *string) error
|
||||
Query(query QueryMesh, reply *string) error
|
||||
PutDescription(description string, reply *string) error
|
||||
PutAlias(alias string, reply *string) error
|
||||
|
@ -91,7 +91,11 @@ func (m *WgMeshConfigApplyer) convertMeshNode(params convertMeshNodeParams) (*wg
|
||||
return p.PublicKey.String() == pubKey.String()
|
||||
})
|
||||
|
||||
endpoint, err := net.ResolveUDPAddr("udp", params.node.GetWgEndpoint())
|
||||
var endpoint *net.UDPAddr = nil
|
||||
|
||||
if params.node.GetType() == conf.PEER_ROLE {
|
||||
endpoint, err = net.ResolveUDPAddr("udp", params.node.GetWgEndpoint())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1,77 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tim-beatham/wgmesh/pkg/graph"
|
||||
"github.com/tim-beatham/wgmesh/pkg/lib"
|
||||
)
|
||||
|
||||
// MeshGraphConverter converts a mesh to a graph
|
||||
type MeshGraphConverter interface {
|
||||
// convert the mesh to textual form
|
||||
Generate(meshId string) (string, error)
|
||||
}
|
||||
|
||||
type MeshDOTConverter struct {
|
||||
manager MeshManager
|
||||
}
|
||||
|
||||
func (c *MeshDOTConverter) Generate(meshId string) (string, error) {
|
||||
mesh := c.manager.GetMesh(meshId)
|
||||
|
||||
if mesh == nil {
|
||||
return "", errors.New("mesh does not exist")
|
||||
}
|
||||
|
||||
g := graph.NewGraph(meshId, graph.GRAPH)
|
||||
|
||||
snapshot, err := mesh.GetMesh()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, node := range snapshot.GetNodes() {
|
||||
c.graphNode(g, node, meshId)
|
||||
}
|
||||
|
||||
nodes := lib.MapValues(snapshot.GetNodes())
|
||||
|
||||
for i, node1 := range nodes[:len(nodes)-1] {
|
||||
for _, node2 := range nodes[i+1:] {
|
||||
if node1.GetWgEndpoint() == node2.GetWgEndpoint() {
|
||||
continue
|
||||
}
|
||||
|
||||
node1Id := fmt.Sprintf("\"%s\"", node1.GetIdentifier())
|
||||
node2Id := fmt.Sprintf("\"%s\"", node2.GetIdentifier())
|
||||
g.AddEdge(fmt.Sprintf("%s to %s", node1Id, node2Id), node1Id, node2Id)
|
||||
}
|
||||
}
|
||||
|
||||
return g.GetDOT()
|
||||
}
|
||||
|
||||
// graphNode: graphs a node within the mesh
|
||||
func (c *MeshDOTConverter) graphNode(g *graph.Graph, node MeshNode, meshId string) {
|
||||
nodeId := fmt.Sprintf("\"%s\"", node.GetIdentifier())
|
||||
g.PutNode(nodeId, graph.CIRCLE)
|
||||
|
||||
self, _ := c.manager.GetSelf(meshId)
|
||||
|
||||
if NodeEquals(self, node) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, route := range node.GetRoutes() {
|
||||
routeId := fmt.Sprintf("\"%s\"", route)
|
||||
g.PutNode(routeId, graph.HEXAGON)
|
||||
g.AddEdge(fmt.Sprintf("%s to %s", nodeId, routeId), nodeId, routeId)
|
||||
}
|
||||
}
|
||||
|
||||
func NewMeshDotConverter(m MeshManager) MeshGraphConverter {
|
||||
return &MeshDOTConverter{manager: m}
|
||||
}
|
@ -182,19 +182,6 @@ func (n *IpcHandler) GetMesh(meshId string, reply *ipc.GetMeshReply) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *IpcHandler) GetDOT(meshId string, reply *string) error {
|
||||
g := mesh.NewMeshDotConverter(n.Server.GetMeshManager())
|
||||
|
||||
result, err := g.Generate(meshId)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*reply = result
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *IpcHandler) Query(params ipc.QueryMesh, reply *string) error {
|
||||
queryResponse, err := n.Server.GetQuerier().Query(params.MeshId, params.Query)
|
||||
|
||||
|
@ -54,9 +54,6 @@ func (s *SyncerImpl) Sync(meshId string) error {
|
||||
s.manager.GetRouteManager().UpdateRoutes()
|
||||
|
||||
publicKey := s.manager.GetPublicKey()
|
||||
|
||||
logging.Log.WriteInfof(publicKey.String())
|
||||
|
||||
nodeNames := correspondingMesh.GetPeers()
|
||||
|
||||
if self != nil {
|
||||
@ -68,13 +65,15 @@ func (s *SyncerImpl) Sync(meshId string) error {
|
||||
var gossipNodes []string
|
||||
|
||||
// Clients always pings its peer for configuration
|
||||
if self != nil && self.GetType() == conf.CLIENT_ROLE {
|
||||
keyFunc := lib.HashString
|
||||
bucketFunc := lib.HashString
|
||||
if self != nil && self.GetType() == conf.CLIENT_ROLE && len(nodeNames) > 1 {
|
||||
neighbours := s.cluster.GetNeighbours(nodeNames, publicKey.String())
|
||||
|
||||
neighbour := lib.ConsistentHash(nodeNames, publicKey.String(), keyFunc, bucketFunc)
|
||||
gossipNodes = make([]string, 1)
|
||||
gossipNodes[0] = neighbour
|
||||
if len(neighbours) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
redundancyLength := min(len(neighbours), 3)
|
||||
gossipNodes = neighbours[:redundancyLength]
|
||||
} else {
|
||||
neighbours := s.cluster.GetNeighbours(nodeNames, publicKey.String())
|
||||
gossipNodes = lib.RandomSubsetOfLength(neighbours, s.conf.BranchRate)
|
||||
@ -92,17 +91,17 @@ func (s *SyncerImpl) Sync(meshId string) error {
|
||||
|
||||
if correspondingPeer == nil {
|
||||
logging.Log.WriteErrorf("node %s does not exist", node)
|
||||
continue
|
||||
}
|
||||
|
||||
err := s.requester.SyncMesh(meshId, correspondingPeer)
|
||||
|
||||
if err == nil || err == io.EOF {
|
||||
succeeded = true
|
||||
} else {
|
||||
// If the synchronisation operation has failed them mark a gravestone
|
||||
// preventing the peer from being re-contacted until it has updated
|
||||
// itself
|
||||
s.manager.GetMesh(meshId).Mark(node)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logging.Log.WriteInfof(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/tim-beatham/wgmesh/pkg/conn"
|
||||
logging "github.com/tim-beatham/wgmesh/pkg/log"
|
||||
"github.com/tim-beatham/wgmesh/pkg/mesh"
|
||||
"google.golang.org/grpc/codes"
|
||||
@ -15,11 +16,34 @@ type SyncErrorHandler interface {
|
||||
// SyncErrorHandlerImpl Is an implementation of the SyncErrorHandler
|
||||
type SyncErrorHandlerImpl struct {
|
||||
meshManager mesh.MeshManager
|
||||
connManager conn.ConnectionManager
|
||||
}
|
||||
|
||||
func (s *SyncErrorHandlerImpl) handleFailed(meshId string, nodeId string) bool {
|
||||
mesh := s.meshManager.GetMesh(meshId)
|
||||
mesh.Mark(nodeId)
|
||||
node, err := mesh.GetNode(nodeId)
|
||||
|
||||
if err != nil {
|
||||
s.connManager.RemoveConnection(node.GetHostEndpoint())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SyncErrorHandlerImpl) handleDeadlineExceeded(meshId string, nodeId string) bool {
|
||||
mesh := s.meshManager.GetMesh(meshId)
|
||||
|
||||
if mesh == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
node, err := mesh.GetNode(nodeId)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
s.connManager.RemoveConnection(node.GetHostEndpoint())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -29,13 +53,15 @@ func (s *SyncErrorHandlerImpl) Handle(meshId string, nodeId string, err error) b
|
||||
logging.Log.WriteInfof("Handled gRPC error: %s", errStatus.Message())
|
||||
|
||||
switch errStatus.Code() {
|
||||
case codes.Unavailable, codes.Unknown, codes.DeadlineExceeded, codes.Internal, codes.NotFound:
|
||||
case codes.Unavailable, codes.Unknown, codes.Internal, codes.NotFound:
|
||||
return s.handleFailed(meshId, nodeId)
|
||||
case codes.DeadlineExceeded:
|
||||
return s.handleDeadlineExceeded(meshId, nodeId)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewSyncErrorHandler(m mesh.MeshManager) SyncErrorHandler {
|
||||
return &SyncErrorHandlerImpl{meshManager: m}
|
||||
func NewSyncErrorHandler(m mesh.MeshManager, conn conn.ConnectionManager) SyncErrorHandler {
|
||||
return &SyncErrorHandlerImpl{meshManager: m, connManager: conn}
|
||||
}
|
||||
|
@ -151,6 +151,6 @@ func (s *SyncRequesterImpl) syncMesh(mesh mesh.MeshProvider, ctx context.Context
|
||||
}
|
||||
|
||||
func NewSyncRequester(s *ctrlserver.MeshCtrlServer) SyncRequester {
|
||||
errorHdlr := NewSyncErrorHandler(s.MeshManager)
|
||||
errorHdlr := NewSyncErrorHandler(s.MeshManager, s.ConnectionManager)
|
||||
return &SyncRequesterImpl{server: s, errorHdlr: errorHdlr}
|
||||
}
|
||||
|
Reference in New Issue
Block a user