Skip to content

Commit c5e2f2a

Browse files
committed
go/oasis-node/storage: Add new inspect command
1 parent 967c710 commit c5e2f2a

4 files changed

Lines changed: 225 additions & 0 deletions

File tree

.changelog/6427.feature.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
go/oasis-node: Add new storage inspect command
2+
3+
The new command enables inspecting storage databases without starting
4+
all the node services. It is in particular useful for the node operators
5+
doing maintenance, sanity checking and or troubleshooting.

go/oasis-node/cmd/storage/dbs.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import (
1616
runtimeConfig "github.com/oasisprotocol/oasis-core/go/runtime/config"
1717
"github.com/oasisprotocol/oasis-core/go/runtime/history"
1818
"github.com/oasisprotocol/oasis-core/go/storage/api"
19+
mkvsDB "github.com/oasisprotocol/oasis-core/go/storage/mkvs/db"
20+
mkvsAPI "github.com/oasisprotocol/oasis-core/go/storage/mkvs/db/api"
21+
workerStorage "github.com/oasisprotocol/oasis-core/go/worker/storage"
1922
)
2023

2124
func openConsensusNodeDB(dataDir string) (api.NodeDB, func(), error) {
@@ -75,6 +78,21 @@ func openConsensusStatestore(dataDir string) (state.Store, error) {
7578
return cmtDB.OpenStateStore(stateDB), nil
7679
}
7780

81+
func openRuntimeStateDB(dataDir string, runtimeID common.Namespace) (api.NodeDB, error) {
82+
backendName := config.GlobalConfig.Storage.Backend
83+
rtDir := runtimeConfig.GetRuntimeStateDir(dataDir, runtimeID)
84+
cfg := &mkvsAPI.Config{
85+
DB: workerStorage.GetLocalBackendDBDir(rtDir, backendName),
86+
Namespace: runtimeID,
87+
MaxCacheSize: int64(config.ParseSizeInBytes(config.GlobalConfig.Storage.MaxCacheSize)),
88+
}
89+
ndb, err := mkvsDB.New(backendName, cfg)
90+
if err != nil {
91+
return nil, fmt.Errorf("failed to open nodeDB (runtimeID: %s): %w", runtimeID, err)
92+
}
93+
return ndb, err
94+
}
95+
7896
func openRuntimeLightHistory(dataDir string, rt common.Namespace) (history.History, error) {
7997
rtDir := runtimeConfig.GetRuntimeStateDir(dataDir, rt)
8098
history, err := history.New(rt, rtDir, history.NewNonePrunerFactory(), true)
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package storage
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/oasisprotocol/oasis-core/go/common"
10+
cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common"
11+
roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
12+
"github.com/oasisprotocol/oasis-core/go/runtime/registry"
13+
)
14+
15+
// Status contains results of inspecting all of the node's databases.
16+
type Status struct {
17+
// Consensus is the consensus part of the status.
18+
Consensus ConsensusStatus `json:"consensus"`
19+
// Runtimes is the runtimes part of the status.
20+
Runtimes map[common.Namespace]RuntimeStatus `json:"runtimes"`
21+
}
22+
23+
// ConsensusStatus summarizes the state of the consensus databases.
24+
type ConsensusStatus struct {
25+
// StateDB is the status of the consensus state db (Merkelized key-value store).
26+
StateDB DBStatus `json:"state_db"`
27+
// BlockHistory is the status of the consensus block store.
28+
BlockHistory DBStatus `json:"block_history"`
29+
}
30+
31+
// RuntimeStatus summarizes the state of the runtime databases.
32+
type RuntimeStatus struct {
33+
// StateDB is the status of the runtime state db (Merkelized key-value store).
34+
StateDB DBStatus `json:"state_db"`
35+
// LightHistory is the status of the runtime light history.
36+
LightHistory DBStatus `json:"light_history"`
37+
}
38+
39+
// DBStatus is a database status.
40+
type DBStatus struct {
41+
// Ok is true when the db exists and there was no error inspecting it.
42+
Ok bool `json:"ok"`
43+
// LatestVersion is the version of the most recent item stored in the corresponding db.
44+
LatestVersion uint64 `json:"latest_version"`
45+
// LastRetainedVersion is the version of the oldest item stored in the corresponding db.
46+
LastRetainedVersion uint64 `json:"last_retained_version"`
47+
}
48+
49+
// Inspect inpects node's databases and returns a corresponding storage Status.
50+
//
51+
// The node should not be running once this command is called.
52+
func Inspect(ctx context.Context, dataDir string, runtimes []common.Namespace) (Status, error) {
53+
var status Status
54+
status.Runtimes = make(map[common.Namespace]RuntimeStatus)
55+
56+
func() {
57+
ndb, close, err := openConsensusNodeDB(dataDir)
58+
if err != nil {
59+
logger.Error("failed to open consensus NodeDB", "err", err)
60+
return
61+
}
62+
defer close()
63+
status.Consensus.StateDB.LatestVersion, status.Consensus.StateDB.Ok = ndb.GetLatestVersion()
64+
status.Consensus.StateDB.LastRetainedVersion = ndb.GetEarliestVersion()
65+
}()
66+
67+
func() {
68+
blockstore, err := openConsensusBlockstore(dataDir)
69+
if err != nil {
70+
logger.Error("failed to open consensus blockstore", "err", err)
71+
return
72+
}
73+
defer blockstore.Close()
74+
status.Consensus.BlockHistory.Ok = true
75+
status.Consensus.BlockHistory.LatestVersion = uint64(blockstore.Height())
76+
status.Consensus.BlockHistory.LastRetainedVersion = uint64(blockstore.Base())
77+
}()
78+
79+
for _, rt := range runtimes {
80+
var rtStatus RuntimeStatus
81+
82+
func() {
83+
ndb, err := openRuntimeStateDB(dataDir, rt)
84+
if err != nil {
85+
logger.Error("failed to open runtime state DB", "err", err)
86+
return
87+
}
88+
defer ndb.Close()
89+
rtStatus.StateDB.LatestVersion, rtStatus.StateDB.Ok = ndb.GetLatestVersion()
90+
rtStatus.StateDB.LastRetainedVersion = ndb.GetEarliestVersion()
91+
}()
92+
93+
func() {
94+
history, err := openRuntimeLightHistory(dataDir, rt)
95+
if err != nil {
96+
logger.Error("failed to open light history", "err", err)
97+
return
98+
}
99+
defer history.Close()
100+
101+
latest, err := history.GetCommittedBlock(ctx, roothash.RoundLatest)
102+
if err != nil {
103+
logger.Error("failed to get latest light history block", "err", err)
104+
return
105+
}
106+
rtStatus.LightHistory.LatestVersion = latest.Header.Round
107+
earliest, err := history.GetEarliestBlock(ctx)
108+
if err != nil {
109+
logger.Error("failed to get earliest light history block", "err", err)
110+
return
111+
}
112+
rtStatus.LightHistory.LastRetainedVersion = earliest.Header.Round
113+
rtStatus.LightHistory.Ok = true
114+
}()
115+
116+
status.Runtimes[rt] = rtStatus
117+
}
118+
119+
return status, nil
120+
}
121+
122+
func newInspectCmd() *cobra.Command {
123+
var outputFormat string
124+
125+
cmd := &cobra.Command{
126+
Use: "inspect",
127+
Short: "inpect storage",
128+
PreRunE: func(_ *cobra.Command, args []string) error {
129+
if err := cmdCommon.Init(); err != nil {
130+
cmdCommon.EarlyLogAndExit(err)
131+
}
132+
133+
running, err := cmdCommon.IsNodeRunning()
134+
if err != nil {
135+
return fmt.Errorf("failed to ensure the node is not running: %w", err)
136+
}
137+
if running {
138+
return fmt.Errorf("node is running")
139+
}
140+
141+
return nil
142+
},
143+
RunE: func(cmd *cobra.Command, args []string) error {
144+
runtimes, err := registry.GetConfiguredRuntimeIDs()
145+
if err != nil {
146+
return fmt.Errorf("failed to get configured runtimes: %w", err)
147+
}
148+
status, err := Inspect(cmd.Context(), cmdCommon.DataDir(), runtimes)
149+
if err != nil {
150+
return fmt.Errorf("failed to get storage databases status: %w", err)
151+
}
152+
153+
switch outputFormat {
154+
case "json":
155+
prettyStatus, err := cmdCommon.PrettyJSONMarshal(status)
156+
if err != nil {
157+
return fmt.Errorf("failed to marshal status as JSON: %w", err)
158+
}
159+
fmt.Println(string(prettyStatus))
160+
return nil
161+
case "text", "":
162+
// Fall through to text output.
163+
default:
164+
return fmt.Errorf("unsupported output format: %s (supported: text, json)", outputFormat)
165+
}
166+
167+
fmt.Println("Consensus:")
168+
fmt.Println(" State DB:")
169+
fmt.Println(" Ok: ", status.Consensus.StateDB.Ok)
170+
fmt.Println(" Latest height: ", status.Consensus.StateDB.LatestVersion)
171+
fmt.Println(" Last retained height: ", status.Consensus.StateDB.LastRetainedVersion)
172+
fmt.Println(" Block history:")
173+
fmt.Println(" Ok: ", status.Consensus.BlockHistory.Ok)
174+
fmt.Println(" Latest height: ", status.Consensus.BlockHistory.LatestVersion)
175+
fmt.Println(" Last retained height: ", status.Consensus.BlockHistory.LastRetainedVersion)
176+
177+
if len(status.Runtimes) <= 0 {
178+
return nil
179+
}
180+
181+
fmt.Println("Runtimes:")
182+
for rt, rtStatus := range status.Runtimes {
183+
fmt.Println(" ", rt)
184+
fmt.Println(" State DB:")
185+
fmt.Println(" Ok: ", rtStatus.StateDB.Ok)
186+
fmt.Println(" Latest round: ", rtStatus.StateDB.LatestVersion)
187+
fmt.Println(" Last retained round: ", rtStatus.StateDB.LastRetainedVersion)
188+
fmt.Println(" Light History:")
189+
fmt.Println(" Ok: ", rtStatus.LightHistory.Ok)
190+
fmt.Println(" Latest round: ", rtStatus.LightHistory.LatestVersion)
191+
fmt.Println(" Last retained round: ", rtStatus.LightHistory.LastRetainedVersion)
192+
}
193+
194+
return nil
195+
},
196+
}
197+
198+
cmd.Flags().StringVar(&outputFormat, "output", "text", "output format (text, json)")
199+
200+
return cmd
201+
}

go/oasis-node/cmd/storage/storage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,5 +604,6 @@ func Register(parentCmd *cobra.Command) {
604604
storageCmd.AddCommand(storageRenameNsCmd)
605605
storageCmd.AddCommand(storageCompactCmd)
606606
storageCmd.AddCommand(pruneCmd)
607+
storageCmd.AddCommand(newInspectCmd())
607608
parentCmd.AddCommand(storageCmd)
608609
}

0 commit comments

Comments
 (0)