Skip to content

Commit

Permalink
Support for starting a single beacon or no beacon on node startup (#1097
Browse files Browse the repository at this point in the history
)

* Support for starting a single beacon or no beacon on node startup
To reload a beacon, one can use:
```
drand load --control <node-port> --id <beacon-name>
```
Fixes #1046
* Emit logs when starting node with no active beacons
  • Loading branch information
dlsniper committed Nov 22, 2022
1 parent 2546a99 commit fb9a253
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 24 deletions.
4 changes: 2 additions & 2 deletions cmd/drand-cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ var appCommands = []*cli.Command{
Flags: toArray(folderFlag, tlsCertFlag, tlsKeyFlag,
insecureFlag, controlFlag, privListenFlag, pubListenFlag, metricsFlag,
certsDirFlag, pushFlag, verboseFlag, oldGroupFlag,
skipValidationFlag, jsonFlag,
skipValidationFlag, jsonFlag, beaconIDFlag,
storageTypeFlag, pgDSNFlag),
Action: func(c *cli.Context) error {
banner()
Expand Down Expand Up @@ -393,7 +393,7 @@ var appCommands = []*cli.Command{
{
Name: "load",
Usage: "Launch a sharing protocol from filesystem",
Flags: toArray(controlFlag, beaconIDFlag),
Flags: toArray(controlFlag, beaconIDFlag, insecureFlag),
Action: loadCmd,
},
{
Expand Down
90 changes: 72 additions & 18 deletions cmd/drand-cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,6 @@ func testStartedDrandFunctional(t *testing.T, ctrlPort, rootPath, address string
require.Error(t, err)
}

//nolint:unused // We want to provide convenience functions
func testPing(t *testing.T, ctrlPort string) {
t.Helper()

Expand Down Expand Up @@ -511,13 +510,27 @@ func testStatus(t *testing.T, ctrlPort, beaconID string) {
status := []string{"drand", "util", "status", "--control", ctrlPort, "--id", beaconID}
err = CLI().Run(status)
if err == nil {
break
return
}
time.Sleep(500 * time.Millisecond)
}
require.NoError(t, err)
}

func testFailStatus(t *testing.T, ctrlPort, beaconID string) {
t.Helper()

var err error

t.Logf(" + running STATUS command with %s on beacon [%s]", ctrlPort, beaconID)
for i := 0; i < 3; i++ {
status := []string{"drand", "util", "status", "--control", ctrlPort, "--id", beaconID}
err = CLI().Run(status)
require.Error(t, err)
time.Sleep(500 * time.Millisecond)
}
}

//nolint:unused // We want to provide convenience functions
func testListSchemes(t *testing.T, ctrlPort string) {
t.Helper()
Expand Down Expand Up @@ -698,7 +711,6 @@ func TestDrandListSchemes(t *testing.T) {
}

func TestDrandReloadBeacon(t *testing.T) {
t.Skipf("test fails when error checking commands")
sch := scheme.GetSchemeFromEnv()
beaconID := test.GetBeaconIDFromEnv()

Expand All @@ -707,49 +719,95 @@ func TestDrandReloadBeacon(t *testing.T) {

for i, inst := range instances {
if i == 0 {
inst.shareLeader(t, n, n, 2, beaconID, sch)
inst.shareLeader(t, n, n, 1, beaconID, sch)
} else {
inst.share(t, instances[0].addr, beaconID)
}
time.Sleep(500 * time.Millisecond)
}

t.Log("waiting for initial set up to settle on all nodes")
time.Sleep(3 * time.Second)

defer func() {
for _, inst := range instances {
err := inst.stopAll()
require.NoError(t, err)
// We want to ignore this error, at least until the stop command won't return an error
// when correctly running the stop command.
_ = inst.stopAll()
}
}()

time.Sleep(1 * time.Second)
t.Log("waiting for initial setup to finish")
time.Sleep(5 * time.Second)

// try to reload a beacon which is already loaded
err := instances[3].load(beaconID)
require.Error(t, err)

// wait some time to generate some randomness
time.Sleep(1 * time.Minute)

// Stop beacon process... not the entire node
err = instances[3].stop(beaconID)
require.NoError(t, err)

// check the node is still alive
testPing(t, instances[3].ctrlPort)

t.Log("waiting for beacons to be generated while a beacon process is stopped on a node")
time.Sleep(10 * time.Second)

// reload a beacon
err = instances[3].load(beaconID)
require.NoError(t, err)

// test beacon process status
testStatus(t, instances[3].ctrlPort, beaconID)

time.Sleep(5 * time.Second)
time.Sleep(3 * time.Second)

// test beacon process status
testStatus(t, instances[3].ctrlPort, beaconID)
}

func TestDrandLoadNotPresentBeacon(t *testing.T) {
sch := scheme.GetSchemeFromEnv()
beaconID := test.GetBeaconIDFromEnv()

n := 4
instances := launchDrandInstances(t, n)

for i, inst := range instances {
if i == 0 {
inst.shareLeader(t, n, n, 1, beaconID, sch)
} else {
inst.share(t, instances[0].addr, beaconID)
}
}

t.Log("waiting for initial set up to settle on all nodes")
time.Sleep(3 * time.Second)

defer func() {
for _, inst := range instances {
_ = inst.stopAll()
}
}()

t.Log("waiting for initial setup to finish")
time.Sleep(5 * time.Second)

// Stop beacon process... not the entire node
err := instances[3].stop(beaconID)
require.NoError(t, err)

t.Log("waiting for beacons to be generated while a beacon process is stopped on a node")
time.Sleep(10 * time.Second)

// reload a different beacon
err = instances[3].load("not-a-valid-beacon-name-here")
require.Error(t, err)

// test original beacon process status and hope it's still off
testFailStatus(t, instances[3].ctrlPort, beaconID)
}

func TestDrandStatus(t *testing.T) {
t.Skipf("test fails when error checking commands")
n := 4
Expand Down Expand Up @@ -847,13 +905,11 @@ func (d *drandInstance) stopAll() error {
return CLI().Run([]string{"drand", "stop", "--control", d.ctrlPort})
}

//nolint:unused // We want to provide convenience functions
func (d *drandInstance) stop(beaconID string) error {
return CLI().Run([]string{"drand", "stop", "--control", d.ctrlPort, "--id", beaconID})
}

//nolint:unused // We want to provide convenience functions
func (d *drandInstance) shareLeader(t *testing.T, nodes, threshold, period int, beaconID string, sch scheme.Scheme) {
func (d *drandInstance) shareLeader(t *testing.T, nodes, threshold, periodSeconds int, beaconID string, sch scheme.Scheme) {
t.Helper()

shareArgs := []string{
Expand All @@ -862,7 +918,7 @@ func (d *drandInstance) shareLeader(t *testing.T, nodes, threshold, period int,
"--leader",
"--nodes", strconv.Itoa(nodes),
"--threshold", strconv.Itoa(threshold),
"--period", fmt.Sprintf("%ds", period),
"--period", fmt.Sprintf("%ds", periodSeconds),
"--control", d.ctrlPort,
"--scheme", sch.ID,
"--id", beaconID,
Expand All @@ -874,7 +930,6 @@ func (d *drandInstance) shareLeader(t *testing.T, nodes, threshold, period int,
}()
}

//nolint:unused // We want to provide convenience functions
func (d *drandInstance) share(t *testing.T, leaderURL, beaconID string) {
t.Helper()

Expand All @@ -892,7 +947,6 @@ func (d *drandInstance) share(t *testing.T, leaderURL, beaconID string) {
}()
}

//nolint:unused // We want to provide convenience functions
func (d *drandInstance) load(beaconID string) error {
reloadArgs := []string{
"drand",
Expand Down
7 changes: 6 additions & 1 deletion cmd/drand-cli/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ func startCmd(c *cli.Context) error {
return fmt.Errorf("can't instantiate drand daemon %w", err)
}

singleBeacon := false
if c.IsSet(beaconIDFlag.Name) {
singleBeacon = true
}

// Check stores and start BeaconProcess
err = drandDaemon.LoadBeaconsFromDisk(c.String(metricsFlag.Name))
err = drandDaemon.LoadBeaconsFromDisk(c.String(metricsFlag.Name), singleBeacon, c.String(beaconIDFlag.Name))
if err != nil {
return fmt.Errorf("couldn't load existing beacons: %w", err)
}
Expand Down
26 changes: 23 additions & 3 deletions core/drand_daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,17 @@ func (dd *DrandDaemon) RemoveBeaconHandler(beaconID string, bp *BeaconProcess) {
}

// LoadBeaconsFromDisk checks for existing stores and creates the corresponding BeaconProcess
// accordingly to each stored BeaconID
func (dd *DrandDaemon) LoadBeaconsFromDisk(metricsFlag string) error {
// accordingly to each stored BeaconID.
// When singleBeacon is set, and the singleBeaconName matches one of the stored beacons, then
// only that beacon will be loaded.
// If the singleBeaconName is an empty string, no beacon will be loaded.
func (dd *DrandDaemon) LoadBeaconsFromDisk(metricsFlag string, singleBeacon bool, singleBeaconName string) error {
// Are we trying to start the daemon without any beacon running?
if singleBeacon && singleBeaconName == "" {
dd.log.Warnw("starting daemon with no active beacon")
return nil
}

// Load possible existing stores
stores, err := key.NewFileStores(dd.opts.ConfigFolderMB())
if err != nil {
Expand All @@ -251,7 +260,12 @@ func (dd *DrandDaemon) LoadBeaconsFromDisk(metricsFlag string) error {

metricsHandlers := make([]metrics.Handler, 0, len(stores))

startedAtLeastOne := false
for beaconID, fs := range stores {
if singleBeacon && singleBeaconName != beaconID {
continue
}

bp, err := dd.LoadBeaconFromStore(beaconID, fs)
if err != nil {
return err
Expand All @@ -261,6 +275,12 @@ func (dd *DrandDaemon) LoadBeaconsFromDisk(metricsFlag string) error {
bp.log.Infow("", "metrics", "adding handler")
metricsHandlers = append(metricsHandlers, bp.MetricsHandlerForPeer)
}

startedAtLeastOne = true
}

if !startedAtLeastOne {
dd.log.Warnw("starting daemon with no active beacon")
}

// Start metrics server
Expand All @@ -279,7 +299,7 @@ func (dd *DrandDaemon) LoadBeaconFromDisk(beaconID string) (*BeaconProcess, erro
func (dd *DrandDaemon) LoadBeaconFromStore(beaconID string, store key.Store) (*BeaconProcess, error) {
bp, err := dd.InstantiateBeaconProcess(beaconID, store)
if err != nil {
dd.log.Error("beacon id", beaconID, "can't instantiate randomness beacon. err:", err)
dd.log.Errorw("beacon id", beaconID, "can't instantiate randomness beacon. err:", err)
return nil, err
}

Expand Down

0 comments on commit fb9a253

Please sign in to comment.