forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/swarm/swarm-snapshot: swarm snapshot generator (ethereum#18453)
* cmd/swarm/swarm-snapshot: add binary to create network snapshots * cmd/swarm/swarm-snapshot: refactor and extend tests * p2p/simulations: remove unused triggerChecks func and fix linter * internal/cmdtest: raise the timeout for killing TestCmd * cmd/swarm/swarm-snapshot: add more comments and other minor adjustments * cmd/swarm/swarm-snapshot: remove redundant check in createSnapshot * cmd/swarm/swarm-snapshot: change comment wording * p2p/simulations: revert Simulation.Run from master https://github.com/ethersphere/go-ethereum/pull/1077/files#r247078904 * cmd/swarm/swarm-snapshot: address pr comments * swarm/network/simulations/discovery: removed snapshot write to file * cmd/swarm/swarm-snapshot, swarm/network/simulations: removed redundant connection event check, fixed lint error
- Loading branch information
Showing
6 changed files
with
437 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// Copyright 2018 The go-ethereum Authors | ||
// This file is part of go-ethereum. | ||
// | ||
// go-ethereum is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// go-ethereum is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/log" | ||
"github.com/ethereum/go-ethereum/node" | ||
"github.com/ethereum/go-ethereum/p2p/simulations" | ||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" | ||
"github.com/ethereum/go-ethereum/swarm/network" | ||
"github.com/ethereum/go-ethereum/swarm/network/simulation" | ||
cli "gopkg.in/urfave/cli.v1" | ||
) | ||
|
||
// create is used as the entry function for "create" app command. | ||
func create(ctx *cli.Context) error { | ||
log.PrintOrigins(true) | ||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int("verbosity")), log.StreamHandler(os.Stdout, log.TerminalFormat(true)))) | ||
|
||
if len(ctx.Args()) < 1 { | ||
return errors.New("argument should be the filename to verify or write-to") | ||
} | ||
filename, err := touchPath(ctx.Args()[0]) | ||
if err != nil { | ||
return err | ||
} | ||
return createSnapshot(filename, ctx.Int("nodes"), strings.Split(ctx.String("services"), ",")) | ||
} | ||
|
||
// createSnapshot creates a new snapshot on filesystem with provided filename, | ||
// number of nodes and service names. | ||
func createSnapshot(filename string, nodes int, services []string) (err error) { | ||
log.Debug("create snapshot", "filename", filename, "nodes", nodes, "services", services) | ||
|
||
sim := simulation.New(map[string]simulation.ServiceFunc{ | ||
"bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { | ||
addr := network.NewAddr(ctx.Config.Node()) | ||
kad := network.NewKademlia(addr.Over(), network.NewKadParams()) | ||
hp := network.NewHiveParams() | ||
hp.KeepAliveInterval = time.Duration(200) * time.Millisecond | ||
hp.Discovery = true // discovery must be enabled when creating a snapshot | ||
|
||
config := &network.BzzConfig{ | ||
OverlayAddr: addr.Over(), | ||
UnderlayAddr: addr.Under(), | ||
HiveParams: hp, | ||
} | ||
return network.NewBzz(config, kad, nil, nil, nil), nil, nil | ||
}, | ||
}) | ||
defer sim.Close() | ||
|
||
_, err = sim.AddNodes(nodes) | ||
if err != nil { | ||
return fmt.Errorf("add nodes: %v", err) | ||
} | ||
|
||
err = sim.Net.ConnectNodesRing(nil) | ||
if err != nil { | ||
return fmt.Errorf("connect nodes: %v", err) | ||
} | ||
|
||
ctx, cancelSimRun := context.WithTimeout(context.Background(), 2*time.Minute) | ||
defer cancelSimRun() | ||
if _, err := sim.WaitTillHealthy(ctx); err != nil { | ||
return fmt.Errorf("wait for healthy kademlia: %v", err) | ||
} | ||
|
||
var snap *simulations.Snapshot | ||
if len(services) > 0 { | ||
// If service names are provided, include them in the snapshot. | ||
// But, check if "bzz" service is not among them to remove it | ||
// form the snapshot as it exists on snapshot creation. | ||
var removeServices []string | ||
var wantBzz bool | ||
for _, s := range services { | ||
if s == "bzz" { | ||
wantBzz = true | ||
break | ||
} | ||
} | ||
if !wantBzz { | ||
removeServices = []string{"bzz"} | ||
} | ||
snap, err = sim.Net.SnapshotWithServices(services, removeServices) | ||
} else { | ||
snap, err = sim.Net.Snapshot() | ||
} | ||
if err != nil { | ||
return fmt.Errorf("create snapshot: %v", err) | ||
} | ||
jsonsnapshot, err := json.Marshal(snap) | ||
if err != nil { | ||
return fmt.Errorf("json encode snapshot: %v", err) | ||
} | ||
return ioutil.WriteFile(filename, jsonsnapshot, 0666) | ||
} | ||
|
||
// touchPath creates an empty file and all subdirectories | ||
// that are missing. | ||
func touchPath(filename string) (string, error) { | ||
if path.IsAbs(filename) { | ||
if _, err := os.Stat(filename); err == nil { | ||
// path exists, overwrite | ||
return filename, nil | ||
} | ||
} | ||
|
||
d, f := path.Split(filename) | ||
dir, err := filepath.Abs(filepath.Dir(os.Args[0])) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
_, err = os.Stat(path.Join(dir, filename)) | ||
if err == nil { | ||
// path exists, overwrite | ||
return filename, nil | ||
} | ||
|
||
dirPath := path.Join(dir, d) | ||
filePath := path.Join(dirPath, f) | ||
if d != "" { | ||
err = os.MkdirAll(dirPath, os.ModeDir) | ||
if err != nil { | ||
return "", err | ||
} | ||
} | ||
|
||
return filePath, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// Copyright 2018 The go-ethereum Authors | ||
// This file is part of go-ethereum. | ||
// | ||
// go-ethereum is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// go-ethereum is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"sort" | ||
"strconv" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/ethereum/go-ethereum/p2p/simulations" | ||
) | ||
|
||
// TestSnapshotCreate is a high level e2e test that tests for snapshot generation. | ||
// It runs a few "create" commands with different flag values and loads generated | ||
// snapshot files to validate their content. | ||
func TestSnapshotCreate(t *testing.T) { | ||
for _, v := range []struct { | ||
name string | ||
nodes int | ||
services string | ||
}{ | ||
{ | ||
name: "defaults", | ||
}, | ||
{ | ||
name: "more nodes", | ||
nodes: defaultNodes + 5, | ||
}, | ||
{ | ||
name: "services", | ||
services: "stream,pss,zorglub", | ||
}, | ||
{ | ||
name: "services with bzz", | ||
services: "bzz,pss", | ||
}, | ||
} { | ||
t.Run(v.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
file, err := ioutil.TempFile("", "swarm-snapshot") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer os.Remove(file.Name()) | ||
|
||
if err = file.Close(); err != nil { | ||
t.Error(err) | ||
} | ||
|
||
args := []string{"create"} | ||
if v.nodes > 0 { | ||
args = append(args, "--nodes", strconv.Itoa(v.nodes)) | ||
} | ||
if v.services != "" { | ||
args = append(args, "--services", v.services) | ||
} | ||
testCmd := runSnapshot(t, append(args, file.Name())...) | ||
|
||
testCmd.ExpectExit() | ||
if code := testCmd.ExitStatus(); code != 0 { | ||
t.Fatalf("command exit code %v, expected 0", code) | ||
} | ||
|
||
f, err := os.Open(file.Name()) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer func() { | ||
err := f.Close() | ||
if err != nil { | ||
t.Error("closing snapshot file", "err", err) | ||
} | ||
}() | ||
|
||
b, err := ioutil.ReadAll(f) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
var snap simulations.Snapshot | ||
err = json.Unmarshal(b, &snap) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
wantNodes := v.nodes | ||
if wantNodes == 0 { | ||
wantNodes = defaultNodes | ||
} | ||
gotNodes := len(snap.Nodes) | ||
if gotNodes != wantNodes { | ||
t.Errorf("got %v nodes, want %v", gotNodes, wantNodes) | ||
} | ||
|
||
if len(snap.Conns) == 0 { | ||
t.Error("no connections in a snapshot") | ||
} | ||
|
||
var wantServices []string | ||
if v.services != "" { | ||
wantServices = strings.Split(v.services, ",") | ||
} else { | ||
wantServices = []string{"bzz"} | ||
} | ||
// sort service names so they can be comparable | ||
// as strings to every node sorted services | ||
sort.Strings(wantServices) | ||
|
||
for i, n := range snap.Nodes { | ||
gotServices := n.Node.Config.Services | ||
sort.Strings(gotServices) | ||
if fmt.Sprint(gotServices) != fmt.Sprint(wantServices) { | ||
t.Errorf("got services %v for node %v, want %v", gotServices, i, wantServices) | ||
} | ||
} | ||
|
||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright 2018 The go-ethereum Authors | ||
// This file is part of go-ethereum. | ||
// | ||
// go-ethereum is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// go-ethereum is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package main | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/ethereum/go-ethereum/cmd/utils" | ||
"github.com/ethereum/go-ethereum/log" | ||
cli "gopkg.in/urfave/cli.v1" | ||
) | ||
|
||
var gitCommit string // Git SHA1 commit hash of the release (set via linker flags) | ||
|
||
// default value for "create" command --nodes flag | ||
const defaultNodes = 10 | ||
|
||
func main() { | ||
err := newApp().Run(os.Args) | ||
if err != nil { | ||
log.Error(err.Error()) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
// newApp construct a new instance of Swarm Snapshot Utility. | ||
// Method Run is called on it in the main function and in tests. | ||
func newApp() (app *cli.App) { | ||
app = utils.NewApp(gitCommit, "Swarm Snapshot Utility") | ||
|
||
app.Name = "swarm-snapshot" | ||
app.Usage = "" | ||
|
||
// app flags (for all commands) | ||
app.Flags = []cli.Flag{ | ||
cli.IntFlag{ | ||
Name: "verbosity", | ||
Value: 1, | ||
Usage: "verbosity level", | ||
}, | ||
} | ||
|
||
app.Commands = []cli.Command{ | ||
{ | ||
Name: "create", | ||
Aliases: []string{"c"}, | ||
Usage: "create a swarm snapshot", | ||
Action: create, | ||
// Flags only for "create" command. | ||
// Allow app flags to be specified after the | ||
// command argument. | ||
Flags: append(app.Flags, | ||
cli.IntFlag{ | ||
Name: "nodes", | ||
Value: defaultNodes, | ||
Usage: "number of nodes", | ||
}, | ||
cli.StringFlag{ | ||
Name: "services", | ||
Value: "bzz", | ||
Usage: "comma separated list of services to boot the nodes with", | ||
}, | ||
), | ||
}, | ||
} | ||
|
||
return app | ||
} |
Oops, something went wrong.