2019-01-24 16:26:36 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
2018-11-03 12:17:14 +00:00
|
|
|
// See LICENSE for copying information
|
|
|
|
|
|
|
|
// Package testplanet implements the full network wiring for testing
|
|
|
|
package testplanet
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-11-19 20:39:25 +00:00
|
|
|
"errors"
|
2019-01-10 13:13:27 +00:00
|
|
|
"io"
|
2018-11-03 12:17:14 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2019-02-04 15:40:37 +00:00
|
|
|
"sync"
|
2018-11-20 16:54:52 +00:00
|
|
|
"time"
|
2018-11-03 12:17:14 +00:00
|
|
|
|
2019-01-10 13:13:27 +00:00
|
|
|
"github.com/zeebo/errs"
|
2018-11-03 12:17:14 +00:00
|
|
|
"go.uber.org/zap"
|
2019-02-06 13:19:14 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
2018-11-03 12:17:14 +00:00
|
|
|
|
2019-12-27 11:48:47 +00:00
|
|
|
"storj.io/common/identity"
|
|
|
|
"storj.io/common/identity/testidentity"
|
|
|
|
"storj.io/common/storj"
|
2019-11-21 22:34:49 +00:00
|
|
|
"storj.io/storj/pkg/server"
|
2019-11-14 19:46:15 +00:00
|
|
|
"storj.io/storj/private/dbutil/pgutil"
|
2019-07-28 06:55:36 +01:00
|
|
|
"storj.io/storj/satellite/overlay"
|
2019-11-11 15:05:21 +00:00
|
|
|
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
2019-01-10 13:13:27 +00:00
|
|
|
"storj.io/storj/storagenode"
|
2019-04-03 20:13:39 +01:00
|
|
|
"storj.io/storj/versioncontrol"
|
2018-11-03 12:17:14 +00:00
|
|
|
)
|
|
|
|
|
2019-09-25 16:41:24 +01:00
|
|
|
const defaultInterval = 15 * time.Second
|
|
|
|
|
2019-01-10 13:13:27 +00:00
|
|
|
// Peer represents one of StorageNode or Satellite
|
|
|
|
type Peer interface {
|
|
|
|
ID() storj.NodeID
|
|
|
|
Addr() string
|
2019-07-03 18:29:18 +01:00
|
|
|
URL() storj.NodeURL
|
2019-04-22 10:07:50 +01:00
|
|
|
Local() overlay.NodeDossier
|
2019-01-10 13:13:27 +00:00
|
|
|
|
|
|
|
Run(context.Context) error
|
|
|
|
Close() error
|
|
|
|
}
|
|
|
|
|
2019-02-01 13:32:28 +00:00
|
|
|
// Config describes planet configuration
|
|
|
|
type Config struct {
|
|
|
|
SatelliteCount int
|
|
|
|
StorageNodeCount int
|
|
|
|
UplinkCount int
|
|
|
|
|
2019-04-08 19:15:19 +01:00
|
|
|
IdentityVersion *storj.IDVersion
|
|
|
|
Reconfigure Reconfigure
|
2019-10-18 20:03:10 +01:00
|
|
|
|
2020-02-27 13:31:05 +00:00
|
|
|
Name string
|
|
|
|
NonParallel bool
|
2019-02-01 13:32:28 +00:00
|
|
|
}
|
|
|
|
|
2018-11-03 12:17:14 +00:00
|
|
|
// Planet is a full storj system setup.
|
|
|
|
type Planet struct {
|
2019-10-18 20:03:10 +01:00
|
|
|
id string
|
2018-11-15 08:57:47 +00:00
|
|
|
log *zap.Logger
|
2019-02-01 13:32:28 +00:00
|
|
|
config Config
|
2018-11-03 12:17:14 +00:00
|
|
|
directory string // TODO: ensure that everything is in-memory to speed things up
|
2019-02-06 13:19:14 +00:00
|
|
|
|
|
|
|
started bool
|
|
|
|
shutdown bool
|
2018-11-03 12:17:14 +00:00
|
|
|
|
2019-02-04 15:40:37 +00:00
|
|
|
peers []closablePeer
|
2019-01-10 13:13:27 +00:00
|
|
|
databases []io.Closer
|
2019-02-04 16:56:10 +00:00
|
|
|
uplinks []*Uplink
|
2018-11-03 12:17:14 +00:00
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
VersionControl *versioncontrol.Peer
|
2019-09-17 21:14:49 +01:00
|
|
|
Satellites []*SatelliteSystem
|
2019-04-03 20:13:39 +01:00
|
|
|
StorageNodes []*storagenode.Peer
|
|
|
|
Uplinks []*Uplink
|
2018-11-03 12:17:14 +00:00
|
|
|
|
2019-11-21 22:34:49 +00:00
|
|
|
ReferralManager *server.Server
|
|
|
|
|
2019-04-08 19:15:19 +01:00
|
|
|
identities *testidentity.Identities
|
2019-02-25 07:38:03 +00:00
|
|
|
whitelistPath string // TODO: in-memory
|
2019-01-25 22:33:20 +00:00
|
|
|
|
2019-02-06 13:19:14 +00:00
|
|
|
run errgroup.Group
|
2019-01-25 22:33:20 +00:00
|
|
|
cancel func()
|
2018-11-03 12:17:14 +00:00
|
|
|
}
|
|
|
|
|
2019-02-04 15:40:37 +00:00
|
|
|
type closablePeer struct {
|
|
|
|
peer Peer
|
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
cancel func()
|
|
|
|
|
2019-12-16 16:00:57 +00:00
|
|
|
close sync.Once
|
|
|
|
closed chan error
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func newClosablePeer(peer Peer) closablePeer {
|
|
|
|
return closablePeer{
|
|
|
|
peer: peer,
|
|
|
|
closed: make(chan error, 1),
|
|
|
|
}
|
2019-02-04 15:40:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes safely the peer.
|
|
|
|
func (peer *closablePeer) Close() error {
|
|
|
|
peer.cancel()
|
|
|
|
peer.close.Do(func() {
|
|
|
|
peer.err = peer.peer.Close()
|
2019-12-16 16:00:57 +00:00
|
|
|
<-peer.closed
|
2019-02-04 15:40:37 +00:00
|
|
|
})
|
2019-12-16 16:00:57 +00:00
|
|
|
|
2019-02-04 15:40:37 +00:00
|
|
|
return peer.err
|
|
|
|
}
|
|
|
|
|
2019-02-01 13:32:28 +00:00
|
|
|
// NewCustom creates a new full system with the specified configuration.
|
|
|
|
func NewCustom(log *zap.Logger, config Config) (*Planet, error) {
|
2019-11-11 15:05:21 +00:00
|
|
|
// Clear error in the beginning to avoid issues down the line.
|
|
|
|
if err := satellitedbtest.PostgresDefined(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-08 19:15:19 +01:00
|
|
|
if config.IdentityVersion == nil {
|
|
|
|
version := storj.LatestIDVersion()
|
|
|
|
config.IdentityVersion = &version
|
|
|
|
}
|
2019-02-01 13:32:28 +00:00
|
|
|
|
2018-11-03 12:17:14 +00:00
|
|
|
planet := &Planet{
|
2019-12-10 16:32:54 +00:00
|
|
|
log: log,
|
|
|
|
id: config.Name + "/" + pgutil.CreateRandomTestingSchemaName(6),
|
|
|
|
config: config,
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Reconfigure.Identities != nil {
|
|
|
|
planet.identities = config.Reconfigure.Identities(log, *config.IdentityVersion)
|
|
|
|
} else {
|
|
|
|
planet.identities = testidentity.NewPregeneratedSignedIdentities(*config.IdentityVersion)
|
2018-11-03 12:17:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
planet.directory, err = ioutil.TempDir("", "planet")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-08 19:15:19 +01:00
|
|
|
whitelistPath, err := planet.WriteWhitelist(*config.IdentityVersion)
|
2019-02-25 07:38:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
planet.whitelistPath = whitelistPath
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
planet.VersionControl, err = planet.newVersionControlServer()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.Combine(err, planet.Shutdown())
|
|
|
|
}
|
|
|
|
|
2019-11-21 22:34:49 +00:00
|
|
|
planet.ReferralManager, err = planet.newReferralManager()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.Combine(err, planet.Shutdown())
|
|
|
|
}
|
|
|
|
|
2019-02-01 13:32:28 +00:00
|
|
|
planet.Satellites, err = planet.newSatellites(config.SatelliteCount)
|
2018-11-03 12:17:14 +00:00
|
|
|
if err != nil {
|
2019-01-24 20:28:06 +00:00
|
|
|
return nil, errs.Combine(err, planet.Shutdown())
|
2018-11-03 12:17:14 +00:00
|
|
|
}
|
|
|
|
|
2019-07-03 18:29:18 +01:00
|
|
|
whitelistedSatellites := make(storj.NodeURLs, 0, len(planet.Satellites))
|
2019-03-18 10:55:06 +00:00
|
|
|
for _, satellite := range planet.Satellites {
|
2019-07-03 18:29:18 +01:00
|
|
|
whitelistedSatellites = append(whitelistedSatellites, satellite.URL())
|
2019-03-18 10:55:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
planet.StorageNodes, err = planet.newStorageNodes(config.StorageNodeCount, whitelistedSatellites)
|
2018-11-03 12:17:14 +00:00
|
|
|
if err != nil {
|
2019-01-24 20:28:06 +00:00
|
|
|
return nil, errs.Combine(err, planet.Shutdown())
|
2018-11-03 12:17:14 +00:00
|
|
|
}
|
|
|
|
|
2019-02-04 16:56:10 +00:00
|
|
|
planet.Uplinks, err = planet.newUplinks("uplink", config.UplinkCount, config.StorageNodeCount)
|
2018-11-03 12:17:14 +00:00
|
|
|
if err != nil {
|
2019-01-24 20:28:06 +00:00
|
|
|
return nil, errs.Combine(err, planet.Shutdown())
|
2018-11-03 12:17:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return planet, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start starts all the nodes.
|
|
|
|
func (planet *Planet) Start(ctx context.Context) {
|
2019-01-25 22:33:20 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
planet.cancel = cancel
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
planet.run.Go(func() error {
|
|
|
|
return planet.VersionControl.Run(ctx)
|
|
|
|
})
|
|
|
|
|
2019-11-21 22:34:49 +00:00
|
|
|
if planet.ReferralManager != nil {
|
|
|
|
planet.run.Go(func() error {
|
|
|
|
return planet.ReferralManager.Run(ctx)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-02-04 15:40:37 +00:00
|
|
|
for i := range planet.peers {
|
|
|
|
peer := &planet.peers[i]
|
|
|
|
peer.ctx, peer.cancel = context.WithCancel(ctx)
|
2019-02-06 13:19:14 +00:00
|
|
|
planet.run.Go(func() error {
|
2019-12-16 16:00:57 +00:00
|
|
|
err := peer.peer.Run(peer.ctx)
|
|
|
|
peer.closed <- err
|
|
|
|
close(peer.closed)
|
|
|
|
|
|
|
|
return err
|
2019-02-06 13:19:14 +00:00
|
|
|
})
|
2019-01-10 13:13:27 +00:00
|
|
|
}
|
2019-09-26 14:14:39 +01:00
|
|
|
|
|
|
|
var group errgroup.Group
|
2019-04-22 10:07:50 +01:00
|
|
|
for _, peer := range planet.StorageNodes {
|
2019-09-26 14:14:39 +01:00
|
|
|
peer := peer
|
|
|
|
group.Go(func() error {
|
|
|
|
peer.Storage2.Monitor.Loop.TriggerWait()
|
2020-02-19 18:32:53 +00:00
|
|
|
peer.Contact.Chore.TriggerWait(ctx)
|
|
|
|
return nil
|
2019-09-26 14:14:39 +01:00
|
|
|
})
|
2019-01-25 22:33:20 +00:00
|
|
|
}
|
2020-02-19 18:32:53 +00:00
|
|
|
_ = group.Wait()
|
2019-04-22 10:07:50 +01:00
|
|
|
|
2019-09-19 20:56:34 +01:00
|
|
|
planet.started = true
|
2018-11-03 12:17:14 +00:00
|
|
|
}
|
|
|
|
|
2019-02-04 15:40:37 +00:00
|
|
|
// StopPeer stops a single peer in the planet
|
|
|
|
func (planet *Planet) StopPeer(peer Peer) error {
|
|
|
|
for i := range planet.peers {
|
|
|
|
p := &planet.peers[i]
|
|
|
|
if p.peer == peer {
|
|
|
|
return p.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errors.New("unknown peer")
|
|
|
|
}
|
|
|
|
|
2018-12-12 15:40:33 +00:00
|
|
|
// Size returns number of nodes in the network
|
2019-02-04 16:56:10 +00:00
|
|
|
func (planet *Planet) Size() int { return len(planet.uplinks) + len(planet.peers) }
|
2018-12-12 15:40:33 +00:00
|
|
|
|
2020-02-24 20:13:12 +00:00
|
|
|
// FindNode is a helper to retrieve a storage node record by its node ID.
|
|
|
|
func (planet *Planet) FindNode(nodeID storj.NodeID) *storagenode.Peer {
|
|
|
|
for _, node := range planet.StorageNodes {
|
|
|
|
if node.ID() == nodeID {
|
|
|
|
return node
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-11-03 12:17:14 +00:00
|
|
|
// Shutdown shuts down all the nodes and deletes temporary directories.
|
|
|
|
func (planet *Planet) Shutdown() error {
|
2018-11-19 20:39:25 +00:00
|
|
|
if !planet.started {
|
2019-01-25 22:33:20 +00:00
|
|
|
return errors.New("Start was never called")
|
2018-11-19 20:39:25 +00:00
|
|
|
}
|
2019-02-06 13:19:14 +00:00
|
|
|
if planet.shutdown {
|
|
|
|
panic("double Shutdown")
|
|
|
|
}
|
|
|
|
planet.shutdown = true
|
|
|
|
|
2019-01-25 22:33:20 +00:00
|
|
|
planet.cancel()
|
2018-11-19 20:39:25 +00:00
|
|
|
|
2019-01-25 22:33:20 +00:00
|
|
|
var errlist errs.Group
|
2019-02-06 13:19:14 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
go func() {
|
|
|
|
// TODO: add diagnostics to see what hasn't been properly shut down
|
|
|
|
timer := time.NewTimer(30 * time.Second)
|
|
|
|
defer timer.Stop()
|
|
|
|
select {
|
|
|
|
case <-timer.C:
|
|
|
|
panic("planet took too long to shutdown")
|
|
|
|
case <-ctx.Done():
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
errlist.Add(planet.run.Wait())
|
|
|
|
cancel()
|
|
|
|
|
2018-11-03 12:17:14 +00:00
|
|
|
// shutdown in reverse order
|
2019-02-04 16:56:10 +00:00
|
|
|
for i := len(planet.uplinks) - 1; i >= 0; i-- {
|
|
|
|
node := planet.uplinks[i]
|
2019-01-10 13:13:27 +00:00
|
|
|
errlist.Add(node.Shutdown())
|
|
|
|
}
|
|
|
|
for i := len(planet.peers) - 1; i >= 0; i-- {
|
2019-02-04 15:40:37 +00:00
|
|
|
peer := &planet.peers[i]
|
2019-01-10 13:13:27 +00:00
|
|
|
errlist.Add(peer.Close())
|
|
|
|
}
|
2019-11-25 21:36:36 +00:00
|
|
|
|
2020-01-23 17:54:25 +00:00
|
|
|
for i := len(planet.databases) - 1; i >= 0; i-- {
|
|
|
|
db := planet.databases[i]
|
2019-01-10 13:13:27 +00:00
|
|
|
errlist.Add(db.Close())
|
2018-11-03 12:17:14 +00:00
|
|
|
}
|
2019-11-21 22:34:49 +00:00
|
|
|
|
|
|
|
if planet.ReferralManager != nil {
|
|
|
|
errlist.Add(planet.ReferralManager.Close())
|
|
|
|
}
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
errlist.Add(planet.VersionControl.Close())
|
2019-01-10 13:13:27 +00:00
|
|
|
|
|
|
|
errlist.Add(os.RemoveAll(planet.directory))
|
|
|
|
return errlist.Err()
|
2018-11-03 12:17:14 +00:00
|
|
|
}
|
|
|
|
|
2019-02-01 13:32:28 +00:00
|
|
|
// Identities returns the identity provider for this planet.
|
2019-04-08 19:15:19 +01:00
|
|
|
func (planet *Planet) Identities() *testidentity.Identities {
|
2019-02-01 13:32:28 +00:00
|
|
|
return planet.identities
|
|
|
|
}
|
|
|
|
|
2019-01-02 18:07:49 +00:00
|
|
|
// NewIdentity creates a new identity for a node
|
2019-01-30 20:47:21 +00:00
|
|
|
func (planet *Planet) NewIdentity() (*identity.FullIdentity, error) {
|
2018-11-03 12:17:14 +00:00
|
|
|
return planet.identities.NewIdentity()
|
|
|
|
}
|
|
|
|
|
2019-01-18 13:54:08 +00:00
|
|
|
// NewListener creates a new listener
|
|
|
|
func (planet *Planet) NewListener() (net.Listener, error) {
|
2018-11-03 12:17:14 +00:00
|
|
|
return net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
}
|
2019-02-25 07:38:03 +00:00
|
|
|
|
|
|
|
// WriteWhitelist writes the pregenerated signer's CA cert to a "CA whitelist", PEM-encoded.
|
2019-04-08 19:15:19 +01:00
|
|
|
func (planet *Planet) WriteWhitelist(version storj.IDVersion) (string, error) {
|
2019-02-25 07:38:03 +00:00
|
|
|
whitelistPath := filepath.Join(planet.directory, "whitelist.pem")
|
2019-04-08 19:15:19 +01:00
|
|
|
signer := testidentity.NewPregeneratedSigner(version)
|
2019-02-25 07:38:03 +00:00
|
|
|
err := identity.PeerCAConfig{
|
|
|
|
CertPath: whitelistPath,
|
|
|
|
}.Save(signer.PeerCA())
|
|
|
|
|
|
|
|
return whitelistPath, err
|
|
|
|
}
|