diff --git a/src/control/cmd/ddb/commands_wrapper.go b/src/control/cmd/ddb/commands_wrapper.go index 4101f331b0d..b688dc52ddd 100644 --- a/src/control/cmd/ddb/commands_wrapper.go +++ b/src/control/cmd/ddb/commands_wrapper.go @@ -399,3 +399,22 @@ func (ctx *DdbContext) CsumDump(path string, dst string, epoch uint64) error { /* Run the c code command */ return daosError(ddb_run_csum_dump(&ctx.ctx, &options)) } + +// CsumCheck recomputes the checksum(s) of the currently stored data at the VOS tree path and +// compares them against the checksum(s) already stored on disk, reporting a match/corruption +// verdict. It is read-only: no data or checksums are modified. epoch selects which version to +// check: for a single value akey it is the epoch at or before which the value is fetched, and +// for an array akey it is the maximal epoch of the visible record extent(s) to select. Pass +// math.MaxUint64 (EPOCH_MAX) to check the latest. By default, only corrupted entries are +// printed (plus a final summary); pass verbose to print every entry (matching, corrupted, and +// "no checksum"). +func (ctx *DdbContext) CsumCheck(path string, epoch uint64, verbose bool) error { + /* Set up the options */ + options := C.struct_csum_check_options{} + options.path = C.CString(path) + defer freeString(options.path) + options.epoch = C.uint64_t(epoch) + options.verbose = C.bool(verbose) + /* Run the c code command */ + return daosError(ddb_run_csum_check(&ctx.ctx, &options)) +} diff --git a/src/control/cmd/ddb/ddb_commands.go b/src/control/cmd/ddb/ddb_commands.go index ec173c848e6..b17887e2e54 100644 --- a/src/control/cmd/ddb/ddb_commands.go +++ b/src/control/cmd/ddb/ddb_commands.go @@ -507,4 +507,34 @@ select`, }, Completer: nil, }) + // Command csum_check + app.AddCommand(&grumble.Command{ + Name: "csum_check", + Aliases: nil, + Help: "Check visible checksum(s) against the stored data", + LongHelp: `Recompute the visible checksum(s) at the vos path from the currently +stored data and compare them against the checksum(s) already stored on disk. The vos +path should be a complete path, including the akey and if the value is an array +value it should include the extent. This command is read-only: no data or +checksums are modified. The --epoch flag selects which version of the checksum +to check: for a single value akey it selects the value visible at or before +that epoch, and for an array value it defines the maximal epoch of the visible +record extent to select. By default, nothing is printed unless corruption is +detected: only corrupted entries are printed, followed by a final error +summary; a clean result (matching or no checksum stored) produces no output +at all. Pass --verbose to print every entry regardless of outcome (matching, +no checksum stored, or corrupted), plus a final summary line even on success`, + HelpGroup: "vos", + Args: func(a *grumble.Args) { + a.String("path", "VOS tree path to check.") + }, + Flags: func(f *grumble.Flags) { + f.Uint64("e", "epoch", math.MaxUint64, "Maximal epoch of the checksum value to select (default EPOCH_MAX).") + f.Bool("v", "verbose", false, "Print every checksum entry, not just corrupted ones.") + }, + Run: func(c *grumble.Context) error { + return ctx.CsumCheck(c.Args.String("path"), c.Flags.Uint64("epoch"), c.Flags.Bool("verbose")) + }, + Completer: nil, + }) } diff --git a/src/control/cmd/ddb/ddb_commands_test.go b/src/control/cmd/ddb/ddb_commands_test.go index 4abd829832f..a7454fcc0ac 100644 --- a/src/control/cmd/ddb/ddb_commands_test.go +++ b/src/control/cmd/ddb/ddb_commands_test.go @@ -133,6 +133,10 @@ func TestDdb_HelpCmds(t *testing.T) { cmdStr: "csum_dump", helpSubStr: "Usage:\n csum_dump [flags] path [dst]\n", }, + "help for 'csum_check' command": { + cmdStr: "csum_check", + helpSubStr: "Usage:\n csum_check [flags] path\n", + }, } { t.Run(name, func(t *testing.T) { ctx := newTestContext(t) @@ -277,6 +281,16 @@ func TestDdb_Cmds(t *testing.T) { } } + csumCheckFnChecking := func(t *testing.T, wantPath string, wantEpoch uint64, wantVerbose bool) func(string, uint64, bool) error { + return func(path string, epoch uint64, verbose bool) error { + fmt.Println("csum_check called") + test.CmpAny(t, "path", wantPath, path) + test.CmpAny(t, "epoch", wantEpoch, epoch) + test.CmpAny(t, "verbose", wantVerbose, verbose) + return nil + } + } + for name, tc := range map[string]struct { args []string setup func(*testing.T) @@ -854,6 +868,58 @@ func TestDdb_Cmds(t *testing.T) { }, expStdout: []string{"csum_dump called"}, }, + + // --- csum_check command --- + "csum_check missing path": { + args: []string{"csum_check"}, + expErr: ddbTestErr("missing argument 'path'"), + }, + "csum_check invalid options": { + args: []string{"csum_check", "--bar"}, + expErr: ddbTestErr("invalid flag: --bar"), + }, + "csum_check default": { + args: []string{"csum_check", "/[0]"}, + setup: func(t *testing.T) { + ddb_run_csum_check_Fn = csumCheckFnChecking(t, "/[0]", math.MaxUint64, false) + }, + expStdout: []string{"csum_check called"}, + }, + "csum_check epoch short": { + args: []string{"csum_check", "-e", "999", "/[0]"}, + setup: func(t *testing.T) { + ddb_run_csum_check_Fn = csumCheckFnChecking(t, "/[0]", 999, false) + }, + expStdout: []string{"csum_check called"}, + }, + "csum_check epoch long": { + args: []string{"csum_check", "--epoch=666", "/[0]"}, + setup: func(t *testing.T) { + ddb_run_csum_check_Fn = csumCheckFnChecking(t, "/[0]", 666, false) + }, + expStdout: []string{"csum_check called"}, + }, + "csum_check verbose short": { + args: []string{"csum_check", "-v", "/[0]"}, + setup: func(t *testing.T) { + ddb_run_csum_check_Fn = csumCheckFnChecking(t, "/[0]", math.MaxUint64, true) + }, + expStdout: []string{"csum_check called"}, + }, + "csum_check verbose long": { + args: []string{"csum_check", "--verbose", "/[0]"}, + setup: func(t *testing.T) { + ddb_run_csum_check_Fn = csumCheckFnChecking(t, "/[0]", math.MaxUint64, true) + }, + expStdout: []string{"csum_check called"}, + }, + "csum_check verbose and epoch": { + args: []string{"csum_check", "-e", "999", "-v", "/[0]"}, + setup: func(t *testing.T) { + ddb_run_csum_check_Fn = csumCheckFnChecking(t, "/[0]", 999, true) + }, + expStdout: []string{"csum_check called"}, + }, } { t.Run(name, func(t *testing.T) { checkCmd := func(t *testing.T, stdout string, err error) { diff --git a/src/control/cmd/ddb/libddb.go b/src/control/cmd/ddb/libddb.go index 8199de61528..e40186b63d7 100644 --- a/src/control/cmd/ddb/libddb.go +++ b/src/control/cmd/ddb/libddb.go @@ -135,3 +135,7 @@ func ddb_run_dtx_aggr(ctx *C.struct_ddb_ctx, opts *C.struct_dtx_aggr_options) C. func ddb_run_csum_dump(ctx *C.struct_ddb_ctx, opts *C.struct_csum_dump_options) C.int { return C.ddb_run_csum_dump(ctx, opts) } + +func ddb_run_csum_check(ctx *C.struct_ddb_ctx, opts *C.struct_csum_check_options) C.int { + return C.ddb_run_csum_check(ctx, opts) +} diff --git a/src/control/cmd/ddb/libddb_stubs.go b/src/control/cmd/ddb/libddb_stubs.go index f28bfcff136..2f309328af9 100644 --- a/src/control/cmd/ddb/libddb_stubs.go +++ b/src/control/cmd/ddb/libddb_stubs.go @@ -60,6 +60,7 @@ func resetDdbStubs() { ddb_run_prov_mem_RC, ddb_run_prov_mem_Fn = 0, nil ddb_run_dtx_aggr_RC, ddb_run_dtx_aggr_Fn = 0, nil ddb_run_csum_dump_RC, ddb_run_csum_dump_Fn = 0, nil + ddb_run_csum_check_RC, ddb_run_csum_check_Fn = 0, nil } var ddb_init_RC C.int = 0 @@ -475,3 +476,19 @@ func ddb_run_csum_dump(_ *C.struct_ddb_ctx, opts *C.struct_csum_dump_options) C. } return ddb_run_csum_dump_RC } + +var ( + ddb_run_csum_check_RC C.int = 0 + ddb_run_csum_check_Fn func(path string, epoch uint64, verbose bool) error +) + +func ddb_run_csum_check(_ *C.struct_ddb_ctx, opts *C.struct_csum_check_options) C.int { + if ddb_run_csum_check_Fn != nil { + return fromGoErr(ddb_run_csum_check_Fn( + C.GoString(opts.path), + uint64(opts.epoch), + bool(opts.verbose), + )) + } + return ddb_run_csum_check_RC +} diff --git a/src/utils/ddb/README.md b/src/utils/ddb/README.md index 3e8d7e3cfc2..25f39f43580 100644 --- a/src/utils/ddb/README.md +++ b/src/utils/ddb/README.md @@ -113,6 +113,8 @@ smd vos close Close the currently opened vos pool shard + csum_check Check visible checksum(s) against the stored data + csum_dump Dump visible checksum(s) dev_list List all devices dev_replace Replace an old device with a new unused device dtx_act_abort Mark the active dtx entry as aborted