Skip to content

Commit

Permalink
[wip] gets save rpc test working
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanlott committed Aug 7, 2023
1 parent 6f2a315 commit 02b2242
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 93 deletions.
8 changes: 6 additions & 2 deletions persistence/blockstore/block_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ package blockstore

import (
"fmt"
"path/filepath"

"github.com/pokt-network/pocket/persistence/kvstore"
"github.com/pokt-network/pocket/shared/codec"
coreTypes "github.com/pokt-network/pocket/shared/core/types"
"github.com/pokt-network/pocket/shared/utils"
)

// backupName is the name of the archive file that is created when Backup is called for a BlockStore
const backupName = "blockstore.bak"

// BlockStore is a key-value store that maps block heights to serialized
// block structures.
// * It manages the atomic state transitions for applying a Unit of Work.
Expand Down Expand Up @@ -93,8 +97,8 @@ func (bs *blockStore) Stop() error {
return bs.kv.Stop()
}

func (bs *blockStore) Backup(path string) error {
return bs.kv.Backup(path)
func (bs *blockStore) Backup(dir string) error {
return bs.kv.Backup(filepath.Join(dir, backupName))
}

///////////////
Expand Down
3 changes: 2 additions & 1 deletion persistence/kvstore/kvstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ type KVStore interface {
Exists(key []byte) (bool, error)
ClearAll() error

Backup(filepath string) error
// Backup takes a directory and makes a backup of the KVStore in that directory.
Backup(dir string) error
}

const (
Expand Down
90 changes: 7 additions & 83 deletions rpc/handlers_node.go
Original file line number Diff line number Diff line change
@@ -1,106 +1,30 @@
package rpc

import (
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"time"

"github.com/labstack/echo/v4"
"github.com/pokt-network/pocket/runtime/configs"
)

// PostV1NodeBackup triggers a backup of the TreeStore, the BlockStore, the PostgreSQL database.
// TECHDEBT: Run each backup process in a goroutine to as elapsed time will become significant
// with the current waterfall approach when even a moderate amount of data resides in each store.
func (s *rpcServer) PostV1NodeBackup(ctx echo.Context) error {
dir := os.TempDir() // TODO_IN_THIS_COMMIT give this a sane default and make it configurable
// TECHDEBT: Wire this up to a default config param if dir == ""
// cfg := s.GetBus().GetRuntimeMgr().GetConfig()

dir := ctx.Param("dir")

s.logger.Info().Msgf("creating backup in %s", dir)

// backup the TreeStore
trees := s.GetBus().GetTreeStore()
if err := trees.Backup(dir); err != nil {
if err := s.GetBus().GetTreeStore().Backup(dir); err != nil {
return err
}

// backup the BlockStore
path := fmt.Sprintf("%s-blockstore-backup.sql", time.Now().String())
if err := s.GetBus().GetPersistenceModule().GetBlockStore().Backup(path); err != nil {
return err
}

// backup the Postgres database
cfg := s.GetBus().GetRuntimeMgr().GetConfig()
err := postgresBackup(cfg, dir) // TODO_IN_THIS_COMMIT make this point at the right directory per the tests
if err != nil {
if err := s.GetBus().GetPersistenceModule().GetBlockStore().Backup(dir); err != nil {
return err
}

s.logger.Info().Msgf("backup created in %s", dir)
return nil
}

func postgresBackup(cfg *configs.Config, dir string) error {
filename := fmt.Sprintf("%s-postgres-backup.sql", time.Now().String())
file, err := os.Create(filepath.Join(dir, filename))
if err != nil {
return err
}
defer file.Close()

// pgurl := cfg.Persistence.PostgresUrl
// credentials, err := parsePostgreSQLConnectionURL(pgurl)
if err != nil {
return err
}

cmd := exec.Command("which pg_dump")
fmt.Printf("cmd.Stdout: %v\n", cmd.Stdout)
fmt.Printf("cmd.Stderr: %v\n", cmd.Stderr)

// cmd := exec.Command(fmt.Sprintf("PGPASSWORD=%s", credentials.password), "pg_dump", "-h", credentials.host, "-U", credentials.username, credentials.dbName)
// cmd.Stdout = file
// err = cmd.Run()
// if err != nil {
// return err
// }

return nil
}

type credentials struct {
username string
password string
host string
dbName string
sslMode string
}

// validate a credentials object for connecting to postgres to create a backup
func parsePostgreSQLConnectionURL(connectionURL string) (*credentials, error) {
parsedURL, err := url.Parse(connectionURL)
if err != nil {
return nil, err
}

if parsedURL.Scheme != "postgres" && parsedURL.Scheme != "postgresql" {
return nil, fmt.Errorf("failed to parse postgres URL")
}

username := parsedURL.User.Username()
password, _ := parsedURL.User.Password()
host := parsedURL.Host
dbName := parsedURL.Path[1:] // Remove the leading slash
query := parsedURL.Query()
sslMode := query.Get("sslmode")

return &credentials{
username: username,
password: password,
host: host,
dbName: dbName,
sslMode: sslMode,
}, nil
}
66 changes: 59 additions & 7 deletions rpc/handlers_test.go
Original file line number Diff line number Diff line change
@@ -1,46 +1,82 @@
package rpc

import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"

"github.com/labstack/echo/v4"
"github.com/pokt-network/pocket/internal/testutil"
"github.com/pokt-network/pocket/logger"
"github.com/pokt-network/pocket/runtime/test_artifacts"
"github.com/pokt-network/pocket/shared/modules"
"github.com/stretchr/testify/require"

"github.com/labstack/echo/v4"
)

func Test_RPCPostV1NodeBackup(t *testing.T) {
tests := []struct {
// THIS WORKS BUT ADJUST LATER
type testCase struct {
name string
setup func(t *testing.T, e echo.Context) *rpcServer
assert func(t *testing.T, tt testCase, e echo.Context, s *rpcServer)
wantErr bool
}{
}

var testDir = t.TempDir()

tests := []testCase{
{
name: "should create a backup in the default directory",
name: "should create a backup in the specified directory",
setup: func(t *testing.T, e echo.Context) *rpcServer {
_, _, url := test_artifacts.SetupPostgresDocker()
pmod := testutil.NewTestPersistenceModule(t, url)
// context := testutil.NewTestPostgresContext(t, pmod, 0)

s := &rpcServer{
logger: *logger.Global.CreateLoggerForModule(modules.RPCModuleName),
}

s.SetBus(pmod.GetBus())

e.SetParamNames("dir")
e.SetParamValues(testDir)

return s
},
wantErr: false,
assert: func(t *testing.T, tt testCase, e echo.Context, s *rpcServer) {
empty, err := isEmpty(testDir)
require.NoError(t, err)
require.False(t, empty)
f, err := os.Open(testDir)
require.NoError(t, err)
dirs, err := f.ReadDir(-1)
require.NoError(t, err)
require.True(t, len(dirs) == 12)

Check failure on line 58 in rpc/handlers_test.go

View workflow job for this annotation

GitHub Actions / lint

ruleguard: use require.Equal instead of require.True (gocritic)

// assert worldstate json was written
_, err = os.Open(filepath.Join(testDir, "worldstate.json"))
require.NoError(t, err)

// assert blockstore was written
_, err = os.Open(filepath.Join(testDir, "blockstore.bak"))
require.NoError(t, err)

// cleanup the directory after each test
t.Cleanup(func() {
require.NoError(t, os.RemoveAll(testDir))
})
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// create a new echo Context for each test
tempDir := t.TempDir()
e := echo.New()
req := httptest.NewRequest(http.MethodPost, tempDir, nil)
req := httptest.NewRequest(http.MethodPost, "/v1/node/backup", nil)

Check failure on line 79 in rpc/handlers_test.go

View workflow job for this annotation

GitHub Actions / lint

httpNoBody: http.NoBody should be preferred to the nil request body (gocritic)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

Expand All @@ -51,6 +87,22 @@ func Test_RPCPostV1NodeBackup(t *testing.T) {
if err := s.PostV1NodeBackup(c); (err != nil) != tt.wantErr {
t.Errorf("rpcServer.PostV1NodeBackup() error = %v, wantErr %v", err, tt.wantErr)
}
tt.assert(t, tt, c, s)
})
}
}

// TECHDEBT(#796) - Organize and dedupe this function into testutil package
func isEmpty(dir string) (bool, error) {
f, err := os.Open(dir)
if err != nil {
return false, err
}
defer f.Close()

_, err = f.Readdirnames(1) // Or f.Readdir(1)
if err == io.EOF {
return true, nil
}
return false, err // Either not empty or error, suits both cases
}

0 comments on commit 02b2242

Please sign in to comment.