Skip to content

Commit

Permalink
netdeploy: allow simple local net topologies
Browse files Browse the repository at this point in the history
  • Loading branch information
algorandskiy committed Jul 26, 2023
1 parent 670010a commit 3ba638c
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 25 deletions.
53 changes: 34 additions & 19 deletions netdeploy/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ import (
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
"github.com/algorand/go-algorand/gen"
"github.com/algorand/go-algorand/libgoal"
"github.com/algorand/go-algorand/netdeploy/remote"
"github.com/algorand/go-algorand/nodecontrol"
"github.com/algorand/go-algorand/util"
"golang.org/x/exp/maps"
)

const configFileName = "network.json"
Expand All @@ -43,8 +45,8 @@ type NetworkCfg struct {
Name string `json:"Name,omitempty"`
// RelayDirs are directories where relays live (where we check for connection IP:Port)
// They are stored relative to root dir (e.g. "Primary")
RelayDirs []string `json:"RelayDirs,omitempty"`
TemplateFile string `json:"TemplateFile,omitempty"` // Template file used to create the network
RelayDirs []string `json:"RelayDirs,omitempty"`
Template NetworkTemplate `json:"Template,omitempty"` // Template file used to create the network
}

// Network represents an instance of a deployed network
Expand Down Expand Up @@ -108,6 +110,7 @@ func CreateNetworkFromTemplate(name, rootDir string, templateReader io.Reader, b
return n, err
}
n.gen = template.Genesis
n.cfg.Template = template

err = n.Save(rootDir)
n.SetConsensus(binDir, consensus)
Expand Down Expand Up @@ -278,9 +281,9 @@ func (n Network) Start(binDir string, redirectOutput bool) error {

// Start Prime Relay and get its listening address

var peerAddressListBuilder strings.Builder
var relayAddress string
var err error
relayNameToAddress := map[string]string{}
for _, relayDir := range n.cfg.RelayDirs {
nodeFullPath := n.getNodeFullPath(relayDir)
nc := nodecontrol.MakeNodeController(binDir, nodeFullPath)
Expand All @@ -299,15 +302,10 @@ func (n Network) Start(binDir string, redirectOutput bool) error {
if err != nil {
return err
}

if peerAddressListBuilder.Len() != 0 {
peerAddressListBuilder.WriteString(";")
}
peerAddressListBuilder.WriteString(relayAddress)
relayNameToAddress[relayDir] = relayAddress
}

peerAddressList := peerAddressListBuilder.String()
err = n.startNodes(binDir, peerAddressList, redirectOutput)
err = n.startNodes(binDir, relayNameToAddress, redirectOutput)
return err
}

Expand Down Expand Up @@ -337,21 +335,38 @@ func (n Network) GetPeerAddresses(binDir string) []string {
if err != nil {
continue
}
if strings.HasPrefix(relayAddress, "http://") {
relayAddress = relayAddress[7:]
}
peerAddresses = append(peerAddresses, relayAddress)
peerAddresses = append(peerAddresses, strings.TrimPrefix(relayAddress, "http://"))
}
return peerAddresses
}

func (n Network) startNodes(binDir, relayAddress string, redirectOutput bool) error {
args := nodecontrol.AlgodStartArgs{
PeerAddress: relayAddress,
RedirectOutput: redirectOutput,
ExitErrorCallback: n.nodeExitCallback,
func (n Network) startNodes(binDir string, relayNameToAddress map[string]string, redirectOutput bool) error {
allRelaysAddresses := strings.Join(maps.Keys(relayNameToAddress), ";")

nodeConfigToEntry := make(map[string]remote.NodeConfigGoal, len(n.cfg.Template.Nodes))
for _, n := range n.cfg.Template.Nodes {
nodeConfigToEntry[n.Name] = n
}

for _, nodeDir := range n.nodeDirs {
args := nodecontrol.AlgodStartArgs{
PeerAddress: allRelaysAddresses,
RedirectOutput: redirectOutput,
ExitErrorCallback: n.nodeExitCallback,
}
if n, ok := nodeConfigToEntry[nodeDir]; ok && len(n.PeerList) > 0 {
relayNames := strings.Split(n.PeerList, ";")
var peerAddresses []string
for _, relayName := range relayNames {
relayAddress, ok := relayNameToAddress[relayName]
if !ok {
return fmt.Errorf("relay %s is not defined in the network", relayName)
}
peerAddresses = append(peerAddresses, relayAddress)
}
args.PeerAddress = strings.Join(peerAddresses, ";")
}

nc := nodecontrol.MakeNodeController(binDir, n.getNodeFullPath(nodeDir))
_, err := nc.StartAlgod(args)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions netdeploy/networkTemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,14 @@ func (t NetworkTemplate) Validate() error {
}

// Follow nodes cannot be relays
// Relays cannot have peer list
for _, cfg := range t.Nodes {
if cfg.IsRelay && isEnableFollowMode(cfg.ConfigJSONOverride) {
return fmt.Errorf("invalid template: follower nodes may not be relays")
}
if cfg.IsRelay && len(cfg.PeerList) > 0 {
return fmt.Errorf("invalid template: relays may not have a peer list")
}
}

if t.Genesis.DevMode && len(t.Nodes) != 1 {
Expand Down
63 changes: 63 additions & 0 deletions netdeploy/networkTemplates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func TestLoadMissingConfig(t *testing.T) {
a := require.New(t)

templateDir, err := filepath.Abs("../test/testdata/nettemplates")
a.Error(err)
template, err := loadTemplate(filepath.Join(templateDir, "<invalidname>.json"))
a.Error(err)
a.Equal(template.Genesis.NetworkName, "")
Expand Down Expand Up @@ -102,6 +103,68 @@ func TestValidate(t *testing.T) {
template, _ = loadTemplate(filepath.Join(templateDir, "TwoNodesOneRelay1000Accounts.json"))
err = template.Validate()
a.NoError(err)

templateDir, _ = filepath.Abs("../test/testdata/nettemplates")
template, _ = loadTemplate(filepath.Join(templateDir, "FiveNodesTwoRelays.json"))
err = template.Validate()
a.NoError(err)
}

func TestPeerListValidate(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

devmodeGenesis := gen.GenesisData{
Wallets: []gen.WalletData{
{
Stake: 100,
},
},
}

t.Run("PeerList is optional", func(t *testing.T) {
t.Parallel()
tmpl := NetworkTemplate{
Genesis: devmodeGenesis,
Nodes: []remote.NodeConfigGoal{
{
IsRelay: true,
},
{
IsRelay: false,
},
},
}
require.NoError(t, tmpl.Validate())
})

t.Run("Relays cannot have PeerList", func(t *testing.T) {
t.Parallel()
tmpl := NetworkTemplate{
Genesis: devmodeGenesis,
Nodes: []remote.NodeConfigGoal{
{
IsRelay: true,
PeerList: "R2",
},
},
}
require.ErrorContains(t, tmpl.Validate(), "relays may not have a peer list")
})

t.Run("Non-relays might have PeerList", func(t *testing.T) {
t.Parallel()
tmpl := NetworkTemplate{
Genesis: devmodeGenesis,
Nodes: []remote.NodeConfigGoal{
{
IsRelay: false,
PeerList: "R2",
},
},
}
require.NoError(t, tmpl.Validate())
})
}

func TestDevModeValidate(t *testing.T) {
Expand Down
18 changes: 12 additions & 6 deletions netdeploy/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/gen"
"github.com/algorand/go-algorand/test/partitiontest"
)

Expand All @@ -34,16 +35,19 @@ func TestSaveNetworkCfg(t *testing.T) {
a := require.New(t)

cfg := NetworkCfg{
Name: "testName",
RelayDirs: []string{"testPND"},
TemplateFile: "testTemplate",
Name: "testName",
RelayDirs: []string{"testPND"},
Template: NetworkTemplate{
Genesis: gen.DefaultGenesis,
},
}

tmpFolder := t.TempDir()
cfgFile := filepath.Join(tmpFolder, configFileName)
err := saveNetworkCfg(cfg, cfgFile)
a.Nil(err)
cfg1, err := loadNetworkCfg(cfgFile)
a.NoError(err)
a.Equal(cfg, cfg1)
}

Expand All @@ -63,9 +67,11 @@ func TestSaveConsensus(t *testing.T) {

net := Network{
cfg: NetworkCfg{
Name: "testName",
RelayDirs: []string{relayDir},
TemplateFile: "testTemplate",
Name: "testName",
RelayDirs: []string{relayDir},
Template: NetworkTemplate{
Genesis: gen.DefaultGenesis,
},
},
nodeDirs: map[string]string{
"node1": nodeDir,
Expand Down
1 change: 1 addition & 0 deletions netdeploy/remote/nodeConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,5 @@ type NodeConfigGoal struct {
Wallets []NodeWalletData
DeadlockDetection int `json:"-"`
ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete
PeerList string `json:",omitempty"` // Semicolon separated list of peers to connect to. Only applicable for non-relays
}
48 changes: 48 additions & 0 deletions test/testdata/nettemplates/FiveNodesTwoRelays.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"Genesis": {
"NetworkName": "tbd",
"LastPartKeyRound": 5000,
"Wallets": [
{
"Name": "LargeWallet",
"Stake": 85,
"Online": true
},
{
"Name": "SmallWallet",
"Stake": 15,
"Online": true
}
]
},
"Nodes": [
{
"Name": "Relay1",
"IsRelay": true
},
{
"Name": "Relay2",
"IsRelay": true
},
{
"Name": "PartNode1",
"Wallets": [{
"Name": "LargeWallet",
"ParticipationOnly": true
}],
"PeerList": "Relay1;Relay2"
},
{
"Name": "PartNode2",
"Wallets": [{
"Name": "SmallWallet",
"ParticipationOnly": true
}],
"PeerList": "Relay2"
},
{
"Name": "NonPartNode",
"PeerList": "Relay1"
}
]
}

0 comments on commit 3ba638c

Please sign in to comment.