mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-03 14:47:16 +00:00
test: add end-to-end testing framework (#5435)
Partial fix for #5291. For details, see [README.md](https://github.com/tendermint/tendermint/blob/erik/e2e-tests/test/e2e/README.md) and [RFC-001](https://github.com/tendermint/tendermint/blob/master/docs/rfc/rfc-001-end-to-end-testing.md). This only includes a single test case under `test/e2e/tests/`, as a proof of concept - additional test cases will be submitted separately. A randomized testnet generator will also be submitted separately, there a currently just a handful of static testnets under `test/e2e/networks/`. This will eventually replace the current P2P tests and run in CI.
This commit is contained in:
committed by
Erik Grinaker
parent
1b733ea28d
commit
a58454e788
@@ -0,0 +1,127 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
// Manifest represents a TOML testnet manifest.
|
||||
type Manifest struct {
|
||||
// IPv6 uses IPv6 networking instead of IPv4. Defaults to IPv4.
|
||||
IPv6 bool `toml:"ipv6"`
|
||||
|
||||
// InitialHeight specifies the initial block height, set in genesis. Defaults to 1.
|
||||
InitialHeight int64 `toml:"initial_height"`
|
||||
|
||||
// InitialState is an initial set of key/value pairs for the application,
|
||||
// set in genesis. Defaults to nothing.
|
||||
InitialState map[string]string `toml:"initial_state"`
|
||||
|
||||
// Validators is the initial validator set in genesis, given as node names
|
||||
// and power:
|
||||
//
|
||||
// validators = { validator01 = 10; validator02 = 20; validator03 = 30 }
|
||||
//
|
||||
// Defaults to all nodes that have mode=validator at power 100. Explicitly
|
||||
// specifying an empty set will start with no validators in genesis, and
|
||||
// the application must return the validator set in InitChain via the
|
||||
// setting validator_update.0 (see below).
|
||||
Validators *map[string]int64
|
||||
|
||||
// ValidatorUpdates is a map of heights to validator names and their power,
|
||||
// and will be returned by the ABCI application. For example, the following
|
||||
// changes the power of validator01 and validator02 at height 1000:
|
||||
//
|
||||
// [validator_update.1000]
|
||||
// validator01 = 20
|
||||
// validator02 = 10
|
||||
//
|
||||
// Specifying height 0 returns the validator update during InitChain. The
|
||||
// application returns the validator updates as-is, i.e. removing a
|
||||
// validator must be done by returning it with power 0, and any validators
|
||||
// not specified are not changed.
|
||||
ValidatorUpdates map[string]map[string]int64 `toml:"validator_update"`
|
||||
|
||||
// Nodes specifies the network nodes. At least one node must be given.
|
||||
Nodes map[string]ManifestNode `toml:"node"`
|
||||
}
|
||||
|
||||
// ManifestNode represents a node in a testnet manifest.
|
||||
type ManifestNode struct {
|
||||
// Mode specifies the type of node: "validator", "full", or "seed". Defaults to
|
||||
// "validator". Full nodes do not get a signing key (a dummy key is generated),
|
||||
// and seed nodes run in seed mode with the PEX reactor enabled.
|
||||
Mode string
|
||||
|
||||
// Seeds is the list of node names to use as P2P seed nodes. Defaults to none.
|
||||
Seeds []string
|
||||
|
||||
// PersistentPeers is a list of node names to maintain persistent P2P
|
||||
// connections to. If neither seeds nor persistent peers are specified,
|
||||
// this defaults to all other nodes in the network.
|
||||
PersistentPeers []string `toml:"persistent_peers"`
|
||||
|
||||
// Database specifies the database backend: "goleveldb", "cleveldb",
|
||||
// "rocksdb", "boltdb", or "badgerdb". Defaults to goleveldb.
|
||||
Database string
|
||||
|
||||
// ABCIProtocol specifies the protocol used to communicate with the ABCI
|
||||
// application: "unix", "tcp", "grpc", or "builtin". Defaults to unix.
|
||||
// builtin will build a complete Tendermint node into the application and
|
||||
// launch it instead of launching a separate Tendermint process.
|
||||
ABCIProtocol string `toml:"abci_protocol"`
|
||||
|
||||
// PrivvalProtocol specifies the protocol used to sign consensus messages:
|
||||
// "file", "unix", or "tcp". Defaults to "file". For unix and tcp, the ABCI
|
||||
// application will launch a remote signer client in a separate goroutine.
|
||||
// Only nodes with mode=validator will actually make use of this.
|
||||
PrivvalProtocol string `toml:"privval_protocol"`
|
||||
|
||||
// StartAt specifies the block height at which the node will be started. The
|
||||
// runner will wait for the network to reach at least this block height.
|
||||
StartAt int64 `toml:"start_at"`
|
||||
|
||||
// FastSync specifies the fast sync mode: "" (disable), "v0", "v1", or "v2".
|
||||
// Defaults to disabled.
|
||||
FastSync string `toml:"fast_sync"`
|
||||
|
||||
// StateSync enables state sync. The runner automatically configures trusted
|
||||
// block hashes and RPC servers. At least one node in the network must have
|
||||
// SnapshotInterval set to non-zero, and the state syncing node must have
|
||||
// StartAt set to an appropriate height where a snapshot is available.
|
||||
StateSync bool `toml:"state_sync"`
|
||||
|
||||
// PersistInterval specifies the height interval at which the application
|
||||
// will persist state to disk. Defaults to 1 (every height), setting this to
|
||||
// 0 disables state persistence.
|
||||
PersistInterval *uint64 `toml:"persist_interval"`
|
||||
|
||||
// SnapshotInterval specifies the height interval at which the application
|
||||
// will take state sync snapshots. Defaults to 0 (disabled).
|
||||
SnapshotInterval uint64 `toml:"snapshot_interval"`
|
||||
|
||||
// RetainBlocks specifies the number of recent blocks to retain. Defaults to
|
||||
// 0, which retains all blocks. Must be greater that PersistInterval and
|
||||
// SnapshotInterval.
|
||||
RetainBlocks uint64 `toml:"retain_blocks"`
|
||||
|
||||
// Perturb lists perturbations to apply to the node after it has been
|
||||
// started and synced with the network:
|
||||
//
|
||||
// disconnect: temporarily disconnects the node from the network
|
||||
// kill: kills the node with SIGKILL then restarts it
|
||||
// pause: temporarily pauses (freezes) the node
|
||||
// restart: restarts the node, shutting it down with SIGTERM
|
||||
Perturb []string
|
||||
}
|
||||
|
||||
// LoadManifest loads a testnet manifest from a file.
|
||||
func LoadManifest(file string) (Manifest, error) {
|
||||
manifest := Manifest{}
|
||||
_, err := toml.DecodeFile(file, &manifest)
|
||||
if err != nil {
|
||||
return manifest, fmt.Errorf("failed to load testnet manifest %q: %w", file, err)
|
||||
}
|
||||
return manifest, nil
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
//nolint: gosec
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
|
||||
)
|
||||
|
||||
const (
|
||||
randomSeed int64 = 2308084734268
|
||||
proxyPortFirst uint32 = 5701
|
||||
networkIPv4 = "10.186.73.0/24"
|
||||
networkIPv6 = "fd80:b10c::/48"
|
||||
)
|
||||
|
||||
type Mode string
|
||||
type Protocol string
|
||||
type Perturbation string
|
||||
|
||||
const (
|
||||
ModeValidator Mode = "validator"
|
||||
ModeFull Mode = "full"
|
||||
ModeSeed Mode = "seed"
|
||||
|
||||
ProtocolBuiltin Protocol = "builtin"
|
||||
ProtocolFile Protocol = "file"
|
||||
ProtocolGRPC Protocol = "grpc"
|
||||
ProtocolTCP Protocol = "tcp"
|
||||
ProtocolUNIX Protocol = "unix"
|
||||
|
||||
PerturbationDisconnect Perturbation = "disconnect"
|
||||
PerturbationKill Perturbation = "kill"
|
||||
PerturbationPause Perturbation = "pause"
|
||||
PerturbationRestart Perturbation = "restart"
|
||||
)
|
||||
|
||||
// Testnet represents a single testnet.
|
||||
type Testnet struct {
|
||||
Name string
|
||||
File string
|
||||
Dir string
|
||||
IP *net.IPNet
|
||||
InitialHeight int64
|
||||
InitialState map[string]string
|
||||
Validators map[*Node]int64
|
||||
ValidatorUpdates map[int64]map[*Node]int64
|
||||
Nodes []*Node
|
||||
}
|
||||
|
||||
// Node represents a Tendermint node in a testnet.
|
||||
type Node struct {
|
||||
Name string
|
||||
Testnet *Testnet
|
||||
Mode Mode
|
||||
Key crypto.PrivKey
|
||||
IP net.IP
|
||||
ProxyPort uint32
|
||||
StartAt int64
|
||||
FastSync string
|
||||
StateSync bool
|
||||
Database string
|
||||
ABCIProtocol Protocol
|
||||
PrivvalProtocol Protocol
|
||||
PersistInterval uint64
|
||||
SnapshotInterval uint64
|
||||
RetainBlocks uint64
|
||||
Seeds []*Node
|
||||
PersistentPeers []*Node
|
||||
Perturbations []Perturbation
|
||||
}
|
||||
|
||||
// LoadTestnet loads a testnet from a manifest file, using the filename to
|
||||
// determine the testnet name and directory (from the basename of the file).
|
||||
// The testnet generation must be deterministic, since it is generated
|
||||
// separately by the runner and the test cases. For this reason, testnets use a
|
||||
// random seed to generate e.g. keys.
|
||||
func LoadTestnet(file string) (*Testnet, error) {
|
||||
manifest, err := LoadManifest(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir := strings.TrimSuffix(file, filepath.Ext(file))
|
||||
|
||||
// Set up resource generators. These must be deterministic.
|
||||
netAddress := networkIPv4
|
||||
if manifest.IPv6 {
|
||||
netAddress = networkIPv6
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(netAddress)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid IP network address %q: %w", netAddress, err)
|
||||
}
|
||||
|
||||
ipGen := newIPGenerator(ipNet)
|
||||
keyGen := newKeyGenerator(randomSeed)
|
||||
proxyPortGen := newPortGenerator(proxyPortFirst)
|
||||
|
||||
testnet := &Testnet{
|
||||
Name: filepath.Base(dir),
|
||||
File: file,
|
||||
Dir: dir,
|
||||
IP: ipGen.Network(),
|
||||
InitialHeight: 1,
|
||||
InitialState: manifest.InitialState,
|
||||
Validators: map[*Node]int64{},
|
||||
ValidatorUpdates: map[int64]map[*Node]int64{},
|
||||
Nodes: []*Node{},
|
||||
}
|
||||
if manifest.InitialHeight > 0 {
|
||||
testnet.InitialHeight = manifest.InitialHeight
|
||||
}
|
||||
|
||||
// Set up nodes, in alphabetical order (IPs and ports get same order).
|
||||
nodeNames := []string{}
|
||||
for name := range manifest.Nodes {
|
||||
nodeNames = append(nodeNames, name)
|
||||
}
|
||||
sort.Strings(nodeNames)
|
||||
|
||||
for _, name := range nodeNames {
|
||||
nodeManifest := manifest.Nodes[name]
|
||||
node := &Node{
|
||||
Name: name,
|
||||
Testnet: testnet,
|
||||
Key: keyGen.Generate(),
|
||||
IP: ipGen.Next(),
|
||||
ProxyPort: proxyPortGen.Next(),
|
||||
Mode: ModeValidator,
|
||||
Database: "goleveldb",
|
||||
ABCIProtocol: ProtocolUNIX,
|
||||
PrivvalProtocol: ProtocolFile,
|
||||
StartAt: nodeManifest.StartAt,
|
||||
FastSync: nodeManifest.FastSync,
|
||||
StateSync: nodeManifest.StateSync,
|
||||
PersistInterval: 1,
|
||||
SnapshotInterval: nodeManifest.SnapshotInterval,
|
||||
RetainBlocks: nodeManifest.RetainBlocks,
|
||||
Perturbations: []Perturbation{},
|
||||
}
|
||||
if nodeManifest.Mode != "" {
|
||||
node.Mode = Mode(nodeManifest.Mode)
|
||||
}
|
||||
if nodeManifest.Database != "" {
|
||||
node.Database = nodeManifest.Database
|
||||
}
|
||||
if nodeManifest.ABCIProtocol != "" {
|
||||
node.ABCIProtocol = Protocol(nodeManifest.ABCIProtocol)
|
||||
}
|
||||
if nodeManifest.PrivvalProtocol != "" {
|
||||
node.PrivvalProtocol = Protocol(nodeManifest.PrivvalProtocol)
|
||||
}
|
||||
if nodeManifest.PersistInterval != nil {
|
||||
node.PersistInterval = *nodeManifest.PersistInterval
|
||||
}
|
||||
for _, p := range nodeManifest.Perturb {
|
||||
node.Perturbations = append(node.Perturbations, Perturbation(p))
|
||||
}
|
||||
testnet.Nodes = append(testnet.Nodes, node)
|
||||
}
|
||||
|
||||
// We do a second pass to set up seeds and persistent peers, which allows graph cycles.
|
||||
for _, node := range testnet.Nodes {
|
||||
nodeManifest, ok := manifest.Nodes[node.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to look up manifest for node %q", node.Name)
|
||||
}
|
||||
for _, seedName := range nodeManifest.Seeds {
|
||||
seed := testnet.LookupNode(seedName)
|
||||
if seed == nil {
|
||||
return nil, fmt.Errorf("unknown seed %q for node %q", seedName, node.Name)
|
||||
}
|
||||
node.Seeds = append(node.Seeds, seed)
|
||||
}
|
||||
for _, peerName := range nodeManifest.PersistentPeers {
|
||||
peer := testnet.LookupNode(peerName)
|
||||
if peer == nil {
|
||||
return nil, fmt.Errorf("unknown persistent peer %q for node %q", peerName, node.Name)
|
||||
}
|
||||
node.PersistentPeers = append(node.PersistentPeers, peer)
|
||||
}
|
||||
|
||||
// If there are no seeds or persistent peers specified, default to persistent
|
||||
// connections to all other nodes.
|
||||
if len(node.PersistentPeers) == 0 && len(node.Seeds) == 0 {
|
||||
for _, peer := range testnet.Nodes {
|
||||
if peer.Name == node.Name {
|
||||
continue
|
||||
}
|
||||
node.PersistentPeers = append(node.PersistentPeers, peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up genesis validators. If not specified explicitly, use all validator nodes.
|
||||
if manifest.Validators != nil {
|
||||
for validatorName, power := range *manifest.Validators {
|
||||
validator := testnet.LookupNode(validatorName)
|
||||
if validator == nil {
|
||||
return nil, fmt.Errorf("unknown validator %q", validatorName)
|
||||
}
|
||||
testnet.Validators[validator] = power
|
||||
}
|
||||
} else {
|
||||
for _, node := range testnet.Nodes {
|
||||
if node.Mode == ModeValidator {
|
||||
testnet.Validators[node] = 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up validator updates.
|
||||
for heightStr, validators := range manifest.ValidatorUpdates {
|
||||
height, err := strconv.Atoi(heightStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid validator update height %q: %w", height, err)
|
||||
}
|
||||
valUpdate := map[*Node]int64{}
|
||||
for name, power := range validators {
|
||||
node := testnet.LookupNode(name)
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("unknown validator %q for update at height %v", name, height)
|
||||
}
|
||||
valUpdate[node] = power
|
||||
}
|
||||
testnet.ValidatorUpdates[int64(height)] = valUpdate
|
||||
}
|
||||
|
||||
return testnet, testnet.Validate()
|
||||
}
|
||||
|
||||
// Validate validates a testnet.
|
||||
func (t Testnet) Validate() error {
|
||||
if t.Name == "" {
|
||||
return errors.New("network has no name")
|
||||
}
|
||||
if t.IP == nil {
|
||||
return errors.New("network has no IP")
|
||||
}
|
||||
if len(t.Nodes) == 0 {
|
||||
return errors.New("network has no nodes")
|
||||
}
|
||||
for _, node := range t.Nodes {
|
||||
if err := node.Validate(t); err != nil {
|
||||
return fmt.Errorf("invalid node %q: %w", node.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates a node.
|
||||
func (n Node) Validate(testnet Testnet) error {
|
||||
if n.Name == "" {
|
||||
return errors.New("node has no name")
|
||||
}
|
||||
if n.IP == nil {
|
||||
return errors.New("node has no IP address")
|
||||
}
|
||||
if !testnet.IP.Contains(n.IP) {
|
||||
return fmt.Errorf("node IP %v is not in testnet network %v", n.IP, testnet.IP)
|
||||
}
|
||||
if n.ProxyPort > 0 {
|
||||
if n.ProxyPort <= 1024 {
|
||||
return fmt.Errorf("local port %v must be >1024", n.ProxyPort)
|
||||
}
|
||||
for _, peer := range testnet.Nodes {
|
||||
if peer.Name != n.Name && peer.ProxyPort == n.ProxyPort {
|
||||
return fmt.Errorf("peer %q also has local port %v", peer.Name, n.ProxyPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch n.FastSync {
|
||||
case "", "v0", "v1", "v2":
|
||||
default:
|
||||
return fmt.Errorf("invalid fast sync setting %q", n.FastSync)
|
||||
}
|
||||
switch n.Database {
|
||||
case "goleveldb", "cleveldb", "boltdb", "rocksdb", "badgerdb":
|
||||
default:
|
||||
return fmt.Errorf("invalid database setting %q", n.Database)
|
||||
}
|
||||
switch n.ABCIProtocol {
|
||||
case ProtocolBuiltin, ProtocolUNIX, ProtocolTCP, ProtocolGRPC:
|
||||
default:
|
||||
return fmt.Errorf("invalid ABCI protocol setting %q", n.ABCIProtocol)
|
||||
}
|
||||
switch n.PrivvalProtocol {
|
||||
case ProtocolFile, ProtocolUNIX, ProtocolTCP:
|
||||
default:
|
||||
return fmt.Errorf("invalid privval protocol setting %q", n.PrivvalProtocol)
|
||||
}
|
||||
|
||||
if n.StateSync && n.StartAt == 0 {
|
||||
return errors.New("state synced nodes cannot start at the initial height")
|
||||
}
|
||||
if n.PersistInterval == 0 && n.RetainBlocks > 0 {
|
||||
return errors.New("persist_interval=0 requires retain_blocks=0")
|
||||
}
|
||||
if n.PersistInterval > 1 && n.RetainBlocks > 0 && n.RetainBlocks < n.PersistInterval {
|
||||
return errors.New("persist_interval must be less than or equal to retain_blocks")
|
||||
}
|
||||
if n.SnapshotInterval > 0 && n.RetainBlocks > 0 && n.RetainBlocks < n.SnapshotInterval {
|
||||
return errors.New("snapshot_interval must be less than er equal to retain_blocks")
|
||||
}
|
||||
|
||||
for _, perturbation := range n.Perturbations {
|
||||
switch perturbation {
|
||||
case PerturbationDisconnect, PerturbationKill, PerturbationPause, PerturbationRestart:
|
||||
default:
|
||||
return fmt.Errorf("invalid perturbation %q", perturbation)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupNode looks up a node by name. For now, simply do a linear search.
|
||||
func (t Testnet) LookupNode(name string) *Node {
|
||||
for _, node := range t.Nodes {
|
||||
if node.Name == name {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ArchiveNodes returns a list of archive nodes that start at the initial height
|
||||
// and contain the entire blockchain history. They are used e.g. as light client
|
||||
// RPC servers.
|
||||
func (t Testnet) ArchiveNodes() []*Node {
|
||||
nodes := []*Node{}
|
||||
for _, node := range t.Nodes {
|
||||
if node.Mode != ModeSeed && node.StartAt == 0 && node.RetainBlocks == 0 {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// RandomNode returns a random non-seed node.
|
||||
func (t Testnet) RandomNode() *Node {
|
||||
for {
|
||||
node := t.Nodes[rand.Intn(len(t.Nodes))]
|
||||
if node.Mode != ModeSeed {
|
||||
return node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IPv6 returns true if the testnet is an IPv6 network.
|
||||
func (t Testnet) IPv6() bool {
|
||||
return t.IP.IP.To4() == nil
|
||||
}
|
||||
|
||||
// Address returns a P2P endpoint address for the node.
|
||||
func (n Node) AddressP2P(withID bool) string {
|
||||
ip := n.IP.String()
|
||||
if n.IP.To4() == nil {
|
||||
// IPv6 addresses must be wrapped in [] to avoid conflict with : port separator
|
||||
ip = fmt.Sprintf("[%v]", ip)
|
||||
}
|
||||
addr := fmt.Sprintf("%v:26656", ip)
|
||||
if withID {
|
||||
addr = fmt.Sprintf("%x@%v", n.Key.PubKey().Address().Bytes(), addr)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// Address returns an RPC endpoint address for the node.
|
||||
func (n Node) AddressRPC() string {
|
||||
ip := n.IP.String()
|
||||
if n.IP.To4() == nil {
|
||||
// IPv6 addresses must be wrapped in [] to avoid conflict with : port separator
|
||||
ip = fmt.Sprintf("[%v]", ip)
|
||||
}
|
||||
return fmt.Sprintf("%v:26657", ip)
|
||||
}
|
||||
|
||||
// Client returns an RPC client for a node.
|
||||
func (n Node) Client() (*rpchttp.HTTP, error) {
|
||||
return rpchttp.New(fmt.Sprintf("http://127.0.0.1:%v", n.ProxyPort), "/websocket")
|
||||
}
|
||||
|
||||
// keyGenerator generates pseudorandom Ed25519 keys based on a seed.
|
||||
type keyGenerator struct {
|
||||
random *rand.Rand
|
||||
}
|
||||
|
||||
func newKeyGenerator(seed int64) *keyGenerator {
|
||||
return &keyGenerator{
|
||||
random: rand.New(rand.NewSource(seed)),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *keyGenerator) Generate() crypto.PrivKey {
|
||||
seed := make([]byte, ed25519.SeedSize)
|
||||
|
||||
_, err := io.ReadFull(g.random, seed)
|
||||
if err != nil {
|
||||
panic(err) // this shouldn't happen
|
||||
}
|
||||
|
||||
return ed25519.GenPrivKeyFromSecret(seed)
|
||||
}
|
||||
|
||||
// portGenerator generates local Docker proxy ports for each node.
|
||||
type portGenerator struct {
|
||||
nextPort uint32
|
||||
}
|
||||
|
||||
func newPortGenerator(firstPort uint32) *portGenerator {
|
||||
return &portGenerator{nextPort: firstPort}
|
||||
}
|
||||
|
||||
func (g *portGenerator) Next() uint32 {
|
||||
port := g.nextPort
|
||||
g.nextPort++
|
||||
if g.nextPort == 0 {
|
||||
panic("port overflow")
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
// ipGenerator generates sequential IP addresses for each node, using a random
|
||||
// network address.
|
||||
type ipGenerator struct {
|
||||
network *net.IPNet
|
||||
nextIP net.IP
|
||||
}
|
||||
|
||||
func newIPGenerator(network *net.IPNet) *ipGenerator {
|
||||
nextIP := make([]byte, len(network.IP))
|
||||
copy(nextIP, network.IP)
|
||||
gen := &ipGenerator{network: network, nextIP: nextIP}
|
||||
// Skip network and gateway addresses
|
||||
gen.Next()
|
||||
gen.Next()
|
||||
return gen
|
||||
}
|
||||
|
||||
func (g *ipGenerator) Network() *net.IPNet {
|
||||
n := &net.IPNet{
|
||||
IP: make([]byte, len(g.network.IP)),
|
||||
Mask: make([]byte, len(g.network.Mask)),
|
||||
}
|
||||
copy(n.IP, g.network.IP)
|
||||
copy(n.Mask, g.network.Mask)
|
||||
return n
|
||||
}
|
||||
|
||||
func (g *ipGenerator) Next() net.IP {
|
||||
ip := make([]byte, len(g.nextIP))
|
||||
copy(ip, g.nextIP)
|
||||
for i := len(g.nextIP) - 1; i >= 0; i-- {
|
||||
g.nextIP[i]++
|
||||
if g.nextIP[i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ip
|
||||
}
|
||||
Reference in New Issue
Block a user