Skip to content

Commit

Permalink
feat: add watchdog and monit (#2329)
Browse files Browse the repository at this point in the history
This PR introduces the `r/gnoland/monit` realm, which can be used by an
external tool to verify if everything is working well, including:
- gnokey compatibility (and all the tx/amino/etc)
- networking (rpc)
- realm state persistency (counter should be higher than the previous
value)

In addition to being a good target for an external monitoring agent, the
realm displays (`Render`) some information, including whether the agent
appears to be missing.

- [x] improve ownable (depends on #2330)
- [x] p/demo/watchdog
- [x] r/gnoland/monit
- [ ] ~update contribs/autocounterd~ -> let's @gnolang/devops tackle
this in another PR. -> #1443

---------

Signed-off-by: moul <[email protected]>
Co-authored-by: Miloš Živković <[email protected]>
  • Loading branch information
moul and zivkovicmilos authored Jul 8, 2024
1 parent a40ac61 commit 62fc9b4
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 0 deletions.
3 changes: 3 additions & 0 deletions examples/gno.land/p/demo/watchdog/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module gno.land/p/demo/watchdog

require gno.land/p/demo/uassert v0.0.0-latest
39 changes: 39 additions & 0 deletions examples/gno.land/p/demo/watchdog/watchdog.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package watchdog

import "time"

type Watchdog struct {
Duration time.Duration
lastUpdate time.Time
lastDown time.Time
}

func (w *Watchdog) Alive() {
now := time.Now()
if !w.IsAlive() {
w.lastDown = now
}
w.lastUpdate = now
}

func (w Watchdog) Status() string {
if w.IsAlive() {
return "OK"
}
return "KO"
}

func (w Watchdog) IsAlive() bool {
return time.Since(w.lastUpdate) < w.Duration
}

func (w Watchdog) UpSince() time.Time {
return w.lastDown
}

func (w Watchdog) DownSince() time.Time {
if !w.IsAlive() {
return w.lastUpdate
}
return time.Time{}
}
16 changes: 16 additions & 0 deletions examples/gno.land/p/demo/watchdog/watchdog_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package watchdog

import (
"testing"
"time"

"gno.land/p/demo/uassert"
)

func TestPackage(t *testing.T) {
w := Watchdog{Duration: 5 * time.Minute}
uassert.False(t, w.IsAlive())
w.Alive()
uassert.True(t, w.IsAlive())
// XXX: add more tests when we'll be able to "skip time".
}
8 changes: 8 additions & 0 deletions examples/gno.land/r/gnoland/monit/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module gno.land/r/gnoland/monit

require (
gno.land/p/demo/ownable v0.0.0-latest
gno.land/p/demo/uassert v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/demo/watchdog v0.0.0-latest
)
59 changes: 59 additions & 0 deletions examples/gno.land/r/gnoland/monit/monit.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Package monit links a monitoring system with the chain in both directions.
//
// The agent will periodically call Incr() and verify that the value is always
// higher than the previously known one. The contract will store the last update
// time and use it to detect whether or not the monitoring agent is functioning
// correctly.
package monit

import (
"std"
"time"

"gno.land/p/demo/ownable"
"gno.land/p/demo/ufmt"
"gno.land/p/demo/watchdog"
)

var (
counter int
lastUpdate time.Time
lastCaller std.Address
wd = watchdog.Watchdog{Duration: 5 * time.Minute}
owner = ownable.New() // TODO: replace with -> ownable.NewWithAddress...
watchdogDuration = 5 * time.Minute
)

// Incr increments the counter and informs the watchdog that we're alive.
// This function can be called by anyone.
func Incr() int {
counter++
lastUpdate = time.Now()
lastCaller = std.PrevRealm().Addr()
wd.Alive()
return counter
}

// Reset resets the realm state.
// This function can only be called by the admin.
func Reset() {
if owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner
panic("unauthorized")
}
counter = 0
lastCaller = std.PrevRealm().Addr()
lastUpdate = time.Now()
wd = watchdog.Watchdog{Duration: 5 * time.Minute}
}

func Render(_ string) string {
status := wd.Status()
return ufmt.Sprintf(
"counter=%d\nlast update=%s\nlast caller=%s\nstatus=%s",
counter, lastUpdate, lastCaller, status,
)
}

// TransferOwnership transfers ownership to a new owner. This is a proxy to
// ownable.Ownable.TransferOwnership.
func TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) }
56 changes: 56 additions & 0 deletions examples/gno.land/r/gnoland/monit/monit_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package monit

import (
"testing"

"gno.land/p/demo/uassert"
)

func TestPackage(t *testing.T) {
// initial state, watchdog is KO.
{
expected := `counter=0
last update=0001-01-01 00:00:00 +0000 UTC
last caller=
status=KO`
got := Render("")
uassert.Equal(t, expected, got)
}

// call Incr(), watchdog is OK.
Incr()
Incr()
Incr()
{
expected := `counter=3
last update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001
last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm
status=OK`
got := Render("")
uassert.Equal(t, expected, got)
}

/* XXX: improve tests once we've the missing std.TestSkipTime feature
// wait 1h, watchdog is KO.
use std.TestSkipTime(time.Hour)
{
expected := `counter=3
last update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001
last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm
status=KO`
got := Render("")
uassert.Equal(t, expected, got)
}
// call Incr(), watchdog is OK.
Incr()
{
expected := `counter=4
last update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001
last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm
status=OK`
got := Render("")
uassert.Equal(t, expected, got)
}
*/
}

0 comments on commit 62fc9b4

Please sign in to comment.