diff --git a/src/control/cmd/ddb/ddb_commands.go b/src/control/cmd/ddb/ddb_commands.go index ec173c848e6..1893d82f53c 100644 --- a/src/control/cmd/ddb/ddb_commands.go +++ b/src/control/cmd/ddb/ddb_commands.go @@ -30,6 +30,18 @@ func onlyOne(bools ...bool) bool { return count == 1 } +// lifecycleCmdHelpNote is appended to pool-lifecycle commands' LongHelp; +// see lifecycleCommands in main.go for the full explanation. This text is +// embedded verbatim into the groff man page (see fprintManPage), so no +// line may start with a quote character: groff treats a leading "'" as a +// control character and silently drops the rest of that line. +const lifecycleCmdHelpNote = ` + +This command manages its own pool lifecycle: it does not accept the +top-level --vos_path/--db_path when given as a single bare command +(see 'ddb --help' or the POOL-CONTENT VS. POOL-LIFECYCLE COMMANDS +section of the man page for details).` + func addAppCommands(app *grumble.App, ctx *DdbContext) { // Command: ls app.AddCommand(&grumble.Command{ @@ -57,7 +69,7 @@ func addAppCommands(app *grumble.App, ctx *DdbContext) { Help: "Opens the VOS file at ", LongHelp: `Opens the VOS file at . The '-w' option allows for modifying the VOS file with the rm, load, commit_ilog, etc commands. The path should be an absolute path to the -pool shard. Part of the path is used to determine what the pool uuid is.`, +pool shard. Part of the path is used to determine what the pool uuid is.` + lifecycleCmdHelpNote, HelpGroup: "vos", Flags: func(f *grumble.Flags) { f.Bool("w", "write_mode", false, "Open the VOS file in write mode.") @@ -88,7 +100,7 @@ pool shard. Part of the path is used to determine what the pool uuid is.`, Name: "close", Aliases: nil, Help: "Close the currently opened VOS file", - LongHelp: "", + LongHelp: "Close the currently opened VOS file." + lifecycleCmdHelpNote, HelpGroup: "vos", Run: func(c *grumble.Context) error { return ctx.Close() @@ -245,16 +257,17 @@ the path must include the extent, otherwise, it must not.`, // Command: smd_sync app.AddCommand(&grumble.Command{ Name: "smd_sync", - Aliases: nil, Help: "Restore the SMD file with backup from blob", - LongHelp: "", + LongHelp: "Restore the SMD file with backup from blob." + lifecycleCmdHelpNote, HelpGroup: "smd", + Flags: func(f *grumble.Flags) { + f.String("p", "db_path", "", "Path to the sys db. (default /mnt/daos)") + }, Args: func(a *grumble.Args) { a.String("nvme_conf", "Path to the nvme conf file. (default /mnt/daos/daos_nvme.conf)", grumble.Default("")) - a.String("db_path", "Path to the sys db. (default /mnt/daos)", grumble.Default("")) }, Run: func(c *grumble.Context) error { - return ctx.SmdSync(c.Args.String("nvme_conf"), c.Args.String("db_path")) + return ctx.SmdSync(c.Args.String("nvme_conf"), c.Flags.String("db_path")) }, Completer: nil, }) @@ -324,7 +337,7 @@ the path must include the extent, otherwise, it must not.`, Aliases: nil, Help: "Manage VOS pool features", LongHelp: `Manage VOS pool features. Exactly one of --enable, --disable, or --show must be provided. -If --db_path is provided, a VOS file path must also be given as a positional argument.`, +If --db_path is provided, a VOS file path must also be given as a positional argument.` + lifecycleCmdHelpNote, HelpGroup: "vos", Flags: func(f *grumble.Flags) { f.String("e", "enable", "", "Enable VOS pool features") @@ -356,7 +369,7 @@ If --db_path is provided, a VOS file path must also be given as a positional arg Name: "rm_pool", Aliases: nil, Help: "Remove a VOS pool file.", - LongHelp: "", + LongHelp: "Remove a VOS pool file." + lifecycleCmdHelpNote, HelpGroup: "vos", Flags: func(f *grumble.Flags) { f.String("p", "db_path", "", "Path to the sys db") @@ -388,32 +401,32 @@ If --db_path is provided, a VOS file path must also be given as a positional arg // Command: dev_list app.AddCommand(&grumble.Command{ Name: "dev_list", - Aliases: nil, Help: "List all devices", - LongHelp: "", + LongHelp: "List all devices." + lifecycleCmdHelpNote, HelpGroup: "vos", - Args: func(a *grumble.Args) { - a.String("db_path", "Path to the sys db.") + Flags: func(f *grumble.Flags) { + f.String("p", "db_path", "", "Path to the sys db.") }, Run: func(c *grumble.Context) error { - return ctx.DevList(c.Args.String("db_path")) + return ctx.DevList(c.Flags.String("db_path")) }, Completer: nil, }) // Command dev_replace app.AddCommand(&grumble.Command{ Name: "dev_replace", - Aliases: nil, Help: "Replace an old device with a new unused device", - LongHelp: "", + LongHelp: "Replace an old device with a new unused device." + lifecycleCmdHelpNote, HelpGroup: "vos", + Flags: func(f *grumble.Flags) { + f.String("p", "db_path", "", "Path to the sys db.") + }, Args: func(a *grumble.Args) { - a.String("db_path", "Path to the sys db.") a.String("old_dev", "Old device UUID.") a.String("new_dev", "New device UUID.") }, Run: func(c *grumble.Context) error { - return ctx.DevReplace(c.Args.String("db_path"), c.Args.String("old_dev"), c.Args.String("new_dev")) + return ctx.DevReplace(c.Flags.String("db_path"), c.Args.String("old_dev"), c.Args.String("new_dev")) }, Completer: nil, }) @@ -438,19 +451,18 @@ If --db_path is provided, a VOS file path must also be given as a positional arg // Command prov_mem app.AddCommand(&grumble.Command{ Name: "prov_mem", - Aliases: nil, Help: "Prepare the memory environment for md-on-ssd mode", - LongHelp: "", + LongHelp: "Prepare the memory environment for md-on-ssd mode." + lifecycleCmdHelpNote, HelpGroup: "vos", Flags: func(f *grumble.Flags) { + f.String("p", "db_path", "", "Path to the sys db.") f.Uint("s", "tmpfs_size", 0, "Specify tmpfs size(GiB) for mount. By default, The total size of all VOS files will be used") }, Args: func(a *grumble.Args) { - a.String("db_path", "Path to the sys db.") a.String("tmpfs_mount", "Path to the tmpfs mountpoint.") }, Run: func(c *grumble.Context) error { - return ctx.ProvMem(c.Args.String("db_path"), c.Args.String("tmpfs_mount"), c.Flags.Uint("tmpfs_size")) + return ctx.ProvMem(c.Flags.String("db_path"), c.Args.String("tmpfs_mount"), c.Flags.Uint("tmpfs_size")) }, Completer: nil, }) diff --git a/src/control/cmd/ddb/ddb_commands_test.go b/src/control/cmd/ddb/ddb_commands_test.go index a1b5b8cc635..c3ec0e33ae2 100644 --- a/src/control/cmd/ddb/ddb_commands_test.go +++ b/src/control/cmd/ddb/ddb_commands_test.go @@ -394,7 +394,7 @@ func TestDdb_Cmds(t *testing.T) { // --- prov_mem command: flag conflict --- // -s / --tmpfs_size: short flag -s was consumed as global VosPath before PassAfterNonOption. "prov_mem with tmpfs_size short flag": { - args: []string{"prov_mem", "-s", "10", "/db", "/mnt"}, + args: []string{"prov_mem", "-s", "10", "-p", "/db", "/mnt"}, setup: func(t *testing.T) { ddb_run_prov_mem_Fn = func(dbPath, tmpfsMount string, tmpfsMountSize uint) error { fmt.Println("prov_mem called") @@ -406,6 +406,109 @@ func TestDdb_Cmds(t *testing.T) { }, expStdout: []string{"prov_mem called"}, }, + "prov_mem with long db_path flag": { + args: []string{"prov_mem", "--db_path", "/db", "/mnt"}, + setup: func(t *testing.T) { + ddb_run_prov_mem_Fn = func(dbPath, tmpfsMount string, tmpfsMountSize uint) error { + fmt.Println("prov_mem called") + test.CmpAny(t, "dbPath", "/db", dbPath) + test.CmpAny(t, "tmpfsMount", "/mnt", tmpfsMount) + test.CmpAny(t, "tmpfsMountSize", uint(0), tmpfsMountSize) + return nil + } + }, + expStdout: []string{"prov_mem called"}, + }, + + // --- dev_list command --- + "dev_list with short db_path flag": { + args: []string{"dev_list", "-p", "/db"}, + setup: func(t *testing.T) { + ddb_run_dev_list_Fn = func(dbPath string) error { + fmt.Println("dev_list called") + test.CmpAny(t, "dbPath", "/db", dbPath) + return nil + } + }, + expStdout: []string{"dev_list called"}, + }, + "dev_list with long db_path flag": { + args: []string{"dev_list", "--db_path", "/db"}, + setup: func(t *testing.T) { + ddb_run_dev_list_Fn = func(dbPath string) error { + fmt.Println("dev_list called") + test.CmpAny(t, "dbPath", "/db", dbPath) + return nil + } + }, + expStdout: []string{"dev_list called"}, + }, + + // --- dev_replace command --- + "dev_replace with short db_path flag": { + args: []string{"dev_replace", "-p", "/db", "old-uuid", "new-uuid"}, + setup: func(t *testing.T) { + ddb_run_dev_replace_Fn = func(dbPath, oldDev, newDev string) error { + fmt.Println("dev_replace called") + test.CmpAny(t, "dbPath", "/db", dbPath) + test.CmpAny(t, "oldDev", "old-uuid", oldDev) + test.CmpAny(t, "newDev", "new-uuid", newDev) + return nil + } + }, + expStdout: []string{"dev_replace called"}, + }, + "dev_replace with long db_path flag": { + args: []string{"dev_replace", "--db_path", "/db", "old-uuid", "new-uuid"}, + setup: func(t *testing.T) { + ddb_run_dev_replace_Fn = func(dbPath, oldDev, newDev string) error { + fmt.Println("dev_replace called") + test.CmpAny(t, "dbPath", "/db", dbPath) + test.CmpAny(t, "oldDev", "old-uuid", oldDev) + test.CmpAny(t, "newDev", "new-uuid", newDev) + return nil + } + }, + expStdout: []string{"dev_replace called"}, + }, + + // --- smd_sync command --- + "smd_sync with short db_path flag": { + args: []string{"smd_sync", "-p", "/db"}, + setup: func(t *testing.T) { + ddb_run_smd_sync_Fn = func(nvmeConf, dbPath string) error { + fmt.Println("smd_sync called") + test.CmpAny(t, "nvmeConf", "", nvmeConf) + test.CmpAny(t, "dbPath", "/db", dbPath) + return nil + } + }, + expStdout: []string{"smd_sync called"}, + }, + "smd_sync with long db_path flag": { + args: []string{"smd_sync", "--db_path", "/db"}, + setup: func(t *testing.T) { + ddb_run_smd_sync_Fn = func(nvmeConf, dbPath string) error { + fmt.Println("smd_sync called") + test.CmpAny(t, "nvmeConf", "", nvmeConf) + test.CmpAny(t, "dbPath", "/db", dbPath) + return nil + } + }, + expStdout: []string{"smd_sync called"}, + }, + "smd_sync with nvme_conf and db_path flag": { + args: []string{"smd_sync", "--db_path", "/db", "/nvme.conf"}, + setup: func(t *testing.T) { + ddb_run_smd_sync_Fn = func(nvmeConf, dbPath string) error { + fmt.Println("smd_sync called") + test.CmpAny(t, "nvmeConf", "/nvme.conf", nvmeConf) + test.CmpAny(t, "dbPath", "/db", dbPath) + return nil + } + }, + expStdout: []string{"smd_sync called"}, + }, // --- csum_dump command --- "csum_dump missing path": { diff --git a/src/control/cmd/ddb/main.go b/src/control/cmd/ddb/main.go index 639ae7ddab0..0f209484433 100644 --- a/src/control/cmd/ddb/main.go +++ b/src/control/cmd/ddb/main.go @@ -55,6 +55,22 @@ func exitWithError(err error) { os.Exit(1) } +// lifecycleCommands lists the pool-lifecycle commands (as opposed to +// pool-content commands like ls/rm) -- see ddbLongDescription for the full +// explanation of how --vos_path/--db_path apply to each category. Used to +// skip auto-open in run() and to reject top-level flags for a bare command +// in parseOpts() (see vosPathTopLevelLifecycleErr). +var lifecycleCommands = []string{ + "feature", + "open", + "close", + "prov_mem", + "smd_sync", + "rm_pool", + "dev_list", + "dev_replace", +} + type cliOptions struct { WriteMode bool `long:"write_mode" short:"w" description:"Open the VOS file in write mode."` CmdFile string `long:"cmd_file" short:"f" description:"Path to a file containing a sequence of ddb commands to execute."` @@ -92,11 +108,9 @@ shell mode. If neither a single command or '-f' option is provided, then the tool will run in interactive mode. In order to modify the VOS file, the '-w' option must be included. -If the command requires it, the VOS file must be provided with the parameter ---vos_path. The VOS file will be opened before any commands are executed, -except for commands that manage their own pool lifecycle (open, close, feature, -rm_pool, prov_mem, smd_sync, dev_list, dev_replace). See the command-specific -help for details. +ddb's subcommands fall into two categories -- pool-content and pool-lifecycle -- which differ +in how the top-level --vos_path/--db_path options apply to them. See the POOL-CONTENT VS. +POOL-LIFECYCLE COMMANDS section of the manpage for details. A DAOS file system can operate in different modes depending on the available hardware resources. The two primary modes are MD-on-SSD and PMEM. In MD-on-SSD mode (the default), metadata is stored @@ -106,6 +120,7 @@ MODE section of the manpage for details. const grumbleUnknownCmdErr = "unknown command, try 'help'" const runCmdArgsErr = "Cannot use both command file and a command string" +const vosPathTopLevelLifecycleErr = "%q manages its own pool lifecycle and does not accept --vos_path/--db_path as a single bare command; provide its path directly to %q (see 'ddb %s --help'), or use --vos_path/--db_path only in interactive or -f command-file mode to pre-open a pool" const loggerInitErr = "Logging facilities cannot be initialized" const ctxInitErr = "DDB Context cannot be initialized" const vosPathOpenErr = "Error opening VOS path '%s'" @@ -309,6 +324,11 @@ func parseOpts(args []string, ctx *DdbContext) (cliOptions, *flags.Parser, error return opts, nil, errors.New(vosPathMissErr) } + // Reject --vos_path/--db_path for a bare pool-lifecycle command (see lifecycleCommands). + if opts.Args.RunCmd != "" && slices.Contains(lifecycleCommands, opts.Args.RunCmd) && opts.VosPath != "" { + return opts, nil, errors.Errorf(vosPathTopLevelLifecycleErr, opts.Args.RunCmd, opts.Args.RunCmd, opts.Args.RunCmd) + } + return opts, parser, nil } @@ -320,26 +340,14 @@ func run(ctx *DdbContext, log *logging.LeveledLogger, opts cliOptions, parser *f defer cleanup() app := createGrumbleApp(ctx) - if opts.VosPath != "" { - // Commands that manage the pool open/close lifecycle themselves and must - // not have the pool pre-opened by the CLI layer. - noAutoOpen := []string{ - "feature", - "open", - "close", - "prov_mem", - "smd_sync", - "rm_pool", - "dev_list", - "dev_replace", - } - if !slices.Contains(noAutoOpen, opts.Args.RunCmd) { - log.Debugf("Connect to path: %s\n", opts.VosPath) - if err := ctx.Open(string(opts.VosPath), string(opts.SysdbPath), opts.WriteMode); err != nil { - return errors.Wrapf(err, vosPathOpenErr, opts.VosPath) - } - defer closePoolIfOpen(ctx, log) + // !slices.Contains(...) is always true today (parseOpts rejects this + // combo for a bare command); kept as a safe fallback if that ever changes. + if opts.VosPath != "" && !slices.Contains(lifecycleCommands, opts.Args.RunCmd) { + log.Debugf("Connect to path: %s\n", opts.VosPath) + if err := ctx.Open(string(opts.VosPath), string(opts.SysdbPath), opts.WriteMode); err != nil { + return errors.Wrapf(err, vosPathOpenErr, opts.VosPath) } + defer closePoolIfOpen(ctx, log) } if opts.Args.RunCmd != "" || opts.CmdFile != "" { diff --git a/src/control/cmd/ddb/main_test.go b/src/control/cmd/ddb/main_test.go index fb5b0176e3c..fa17797ebdf 100644 --- a/src/control/cmd/ddb/main_test.go +++ b/src/control/cmd/ddb/main_test.go @@ -297,22 +297,12 @@ func openFnCheckingWriteMode(t *testing.T, wantWriteMode bool, called *bool) fun } } -// openFnMustNotBeCalled is a ddb_run_open_Fn stub that fails the test if -// the open function is called at all (used to verify no-auto-open behavior). -func openFnMustNotBeCalled(_ string, _ string, _ bool) error { - return fmt.Errorf("open should not have been called") -} - -// openFnAllowedOnce returns a ddb_run_open_Fn stub that allows the open -// function to be called exactly once (used to verify the 'open' command -// itself calls open but the CLI does not pre-open). -func openFnAllowedOnce() func(string, string, bool) error { - count := 0 - return func(_ string, _ string, _ bool) error { - count++ - if count > 1 { - return fmt.Errorf("open pre-opened by CLI (called %d times)", count) - } +// closeFnChecking returns a ddb_run_close_Fn stub. called is set to true +// when the stub is invoked, allowing the caller to assert that close was +// called. +func closeFnChecking(called *bool) func() error { + return func() error { + *called = true return nil } } @@ -373,58 +363,37 @@ func TestDdb_runDdb(t *testing.T) { t.Cleanup(func() { test.AssertTrue(t, called, "open was not called") }) }, }, - "No auto-open for feature command": { - // noAutoOpen is keyed on opts.Args.RunCmd which is empty in command-file - // mode, so this case only applies to command-line mode. - args: []string{"-s", "/foo/vos-0", "feature", "--show"}, - setup: func(t *testing.T) { - ddb_run_open_Fn = openFnMustNotBeCalled - }, + "Reject top-level flags for feature (pool-lifecycle command)": { + args: []string{"-s", "/foo/vos-0", "feature", "--show"}, + expErr: ddbTestErr(`"feature" manages its own pool lifecycle`), }, - "No auto-open for open command": { - // The CLI should NOT pre-open when the 'open' command is issued; only the - // command itself should call ctx.Open (exactly once). - // Only valid for command-line mode (see note above). - args: []string{"-s", "/foo/vos-0", "open", "/foo/vos-0"}, - setup: func(t *testing.T) { - ddb_run_open_Fn = openFnAllowedOnce() - }, + "Reject top-level flags for open (pool-lifecycle command)": { + args: []string{"-s", "/foo/vos-0", "open", "/foo/vos-0"}, + expErr: ddbTestErr(`"open" manages its own pool lifecycle`), }, - "No auto-open for smd_sync": { - args: []string{"-s", "/foo/vos-0", "smd_sync"}, - setup: func(t *testing.T) { - ddb_run_open_Fn = openFnMustNotBeCalled - }, + "Reject top-level flags for smd_sync (pool-lifecycle command)": { + args: []string{"-s", "/foo/vos-0", "smd_sync"}, + expErr: ddbTestErr(`"smd_sync" manages its own pool lifecycle`), }, - "No auto-open for rm_pool": { - args: []string{"-s", "/foo/vos-0", "rm_pool", "/mnt/rdb-pool"}, - setup: func(t *testing.T) { - ddb_run_open_Fn = openFnMustNotBeCalled - }, + "Reject top-level vos_path and db_path for rm_pool (pool-lifecycle command)": { + args: []string{"-s", "/foo/vos-0", "-p", "/sysdb", "rm_pool", "/mnt/rdb-pool"}, + expErr: ddbTestErr(`"rm_pool" manages its own pool lifecycle`), }, - "No auto-open for close": { - args: []string{"-s", "/foo/vos-0", "close"}, - setup: func(t *testing.T) { - ddb_run_open_Fn = openFnMustNotBeCalled - }, + "Reject top-level flags for close (pool-lifecycle command)": { + args: []string{"-s", "/foo/vos-0", "close"}, + expErr: ddbTestErr(`"close" manages its own pool lifecycle`), }, - "No auto-open for prov_mem": { - args: []string{"-s", "/foo/vos-0", "prov_mem", "/db", "/mnt"}, - setup: func(t *testing.T) { - ddb_run_open_Fn = openFnMustNotBeCalled - }, + "Reject top-level flags for prov_mem (pool-lifecycle command)": { + args: []string{"-s", "/foo/vos-0", "prov_mem", "-p", "/db", "/mnt"}, + expErr: ddbTestErr(`"prov_mem" manages its own pool lifecycle`), }, - "No auto-open for dev_list": { - args: []string{"-s", "/foo/vos-0", "dev_list", "/db"}, - setup: func(t *testing.T) { - ddb_run_open_Fn = openFnMustNotBeCalled - }, + "Reject top-level flags for dev_list (pool-lifecycle command)": { + args: []string{"-s", "/foo/vos-0", "dev_list", "-p", "/db"}, + expErr: ddbTestErr(`"dev_list" manages its own pool lifecycle`), }, - "No auto-open for dev_replace": { - args: []string{"-s", "/foo/vos-0", "dev_replace", "/db", "old-uuid", "new-uuid"}, - setup: func(t *testing.T) { - ddb_run_open_Fn = openFnMustNotBeCalled - }, + "Reject top-level flags for dev_replace (pool-lifecycle command)": { + args: []string{"-s", "/foo/vos-0", "dev_replace", "-p", "/db", "old-uuid", "new-uuid"}, + expErr: ddbTestErr(`"dev_replace" manages its own pool lifecycle`), }, "Init failure": { args: []string{"ls"}, @@ -504,6 +473,19 @@ func TestDdb_runDdbCommandFile(t *testing.T) { t.Cleanup(func() { test.AssertTrue(t, called, "open was not called") }) }, }, + "Top-level flags accepted in -f mode even for a bare pool-lifecycle command": { + flags: []string{"-s", "/foo/vos-0", "-p", "/bar"}, + cmdLine: "close", + setup: func(t *testing.T) { + var openCalled, closeCalled bool + ddb_run_open_Fn = openFnChecking(t, "/foo/vos-0", "/bar", &openCalled) + ddb_run_close_Fn = closeFnChecking(&closeCalled) + t.Cleanup(func() { + test.AssertTrue(t, openCalled, "open was not called") + test.AssertTrue(t, closeCalled, "close was not called") + }) + }, + }, } { t.Run(name, func(t *testing.T) { tmpDir := t.TempDir() diff --git a/src/control/cmd/ddb/manpage.go b/src/control/cmd/ddb/manpage.go index ec30b52f9a3..1f99fe8fdea 100644 --- a/src/control/cmd/ddb/manpage.go +++ b/src/control/cmd/ddb/manpage.go @@ -103,6 +103,42 @@ levels. The available log levels are: \fBTRACE\fR, \fBDEBUG\fR (or \fBDBUG\fR), Logs can be redirected to a file using the \fI--log_dir=\fR option. Note that \fBERROR\fR and above are always printed to the console, even when \fI--log_dir\fR is set.` +const manPoolLifecycleSection = `.SH POOL-CONTENT VS. POOL-LIFECYCLE COMMANDS +.SS Overview +ddb's subcommands fall into two categories, based on how the top-level \fI--vos_path\fR/ +\fI--db_path\fR options apply to them. +.PP +\fBPool-content commands\fR (\fBls\fR, \fBrm\fR, \fBvalue_dump\fR, and most others) operate on +the content of an already-open VOS pool. When one of these is given as a single command or run +from a \fI-f\fR command file, the VOS file is opened before the command executes, using the +top-level \fI--vos_path\fR/\fI--db_path\fR. +.PP +\fBPool-lifecycle commands\fR (\fBopen\fR, \fBclose\fR, \fBfeature\fR, \fBrm_pool\fR, +\fBdev_list\fR, \fBdev_replace\fR, \fBprov_mem\fR, \fBsmd_sync\fR) manage the VOS pool's own +open/close/remove/replace lifecycle themselves, so ddb does not pre-open a pool for them. When +one of these is given as a single bare command directly on the command line, \fI--vos_path\fR/ +\fI--db_path\fR are \fBnot accepted\fR: ddb returns an error and the path must be provided +directly to the command instead (see its own \fI--help\fR). +.PP +In interactive mode or when running a \fI-f\fR command file, \fI--vos_path\fR/\fI--db_path\fR are +accepted: they only ever drive the one-time initial auto-open, and it is up to the user to close the +pre-opened pool (or target a different one) before running a pool-lifecycle command from within that +session or file that requires it to be closed. +.SS Examples +Rejected: \fBrm_pool\fR manages its own pool lifecycle and does not accept the top-level flags +as a bare command. +.sp +.EX + ddb --db_path /path/to/sys/db --vos_path /path/to/vos-0 rm_pool +.EE +.sp +Works: provide \fBrm_pool\fR's own db_path flag and path argument directly. +.sp +.EX + ddb rm_pool --db_path /path/to/sys/db /path/to/vos-0 +.EE +.sp` + const manMdOnSsdSection = `.SH MD-ON-SSD MODE .SS Overview The MD-on-SSD workflow differs from PMEM mode. In PMEM mode, mount points are permanently @@ -117,7 +153,7 @@ tmpfs mount can simply be unmounted to free memory. .PP .SS Synopsis .Vb 1 -\& prov_mem [flags] db_path tmpfs_mount +\& prov_mem [flags] tmpfs_mount .Ve .SS Description This command performs the following steps: @@ -131,26 +167,28 @@ Sets up the necessary directory structure. Recreates VOS pool target files on the tmpfs mount. .SS Arguments .TP -.B db_path -Path to the sys db. -.TP .B tmpfs_mount Path to the tmpfs mountpoint. .SS Flags .TP +.B \-p, \-\-db_path string +Path to the sys db. This command manages its own pool lifecycle and does not accept the +top-level \fI--vos_path\fR/\fI--db_path\fR as a single bare command; see the POOL-CONTENT VS. +POOL-LIFECYCLE COMMANDS section for details. +.TP .B \-s, \-\-tmpfs_size uint Size of the tmpfs mount in GiB. Defaults to the total size of all VOS files. .SS Examples Prepare the memory environment with an auto-calculated tmpfs size: .sp .EX - ddb prov_mem /path/to/sys/db /mnt/tmpfs + ddb prov_mem --db_path /path/to/sys/db /mnt/tmpfs .EE .sp Prepare the memory environment with a specific tmpfs size of 16 GiB: .sp .EX - ddb prov_mem -s 16 /path/to/sys/db /mnt/tmpfs + ddb prov_mem --db_path /path/to/sys/db -s 16 /mnt/tmpfs .EE .sp .SS Notes @@ -188,6 +226,8 @@ func fprintManPage(dest io.Writer, app *grumble.App, parser *flags.Parser) { fmt.Fprintf(dest, ".TP\n.B %s\n%s\n", cmd.Name, cmdHelp) } + fmt.Fprintln(dest, manPoolLifecycleSection) + fmt.Fprintln(dest, manPathSection) fmt.Fprintln(dest, manMdOnSsdSection) diff --git a/src/tests/ftest/util/ddb_utils.py b/src/tests/ftest/util/ddb_utils.py index beea3092606..76451000c5d 100644 --- a/src/tests/ftest/util/ddb_utils.py +++ b/src/tests/ftest/util/ddb_utils.py @@ -287,7 +287,7 @@ def dtx_cmt_clear(self, component_path="[0]"): return self.run() def prov_mem(self, db_path, tmpfs_mount): - """Call ddb --vos_path "" prov_mem . + """Call ddb --vos_path "" prov_mem --db_path . Args: db_path (str): Path to the system database. e.g., @@ -299,7 +299,7 @@ def prov_mem(self, db_path, tmpfs_mount): CommandResult: groups of command results from the same hosts with the same return status """ self.vos_path.value = '""' - cmd = ["prov_mem", db_path, tmpfs_mount] + cmd = ["prov_mem", "--db_path", db_path, tmpfs_mount] self.single_command.value = " ".join(cmd) return self.run() diff --git a/src/utils/ddb/README.md b/src/utils/ddb/README.md index 3e8d7e3cfc2..0a4d0404a49 100644 --- a/src/utils/ddb/README.md +++ b/src/utils/ddb/README.md @@ -138,20 +138,51 @@ vos vea_update Alter the VEA tree to mark a region as free. ``` +### Pool-content commands vs. pool-lifecycle commands + +ddb's subcommands fall into two categories: + +**Pool-content commands** (`ls`, `rm`, `value_dump`, and most others) operate on the content of an +already-open VOS pool. When one of these is given as a single command or run from a `-f` command +file, the VOS file is opened before the command executes, using the top-level `--vos_path`/ +`--db_path`. + +**Pool-lifecycle commands** (`open`, `close`, `feature`, `rm_pool`, `dev_list`, `dev_replace`, +`prov_mem`, `smd_sync`) manage the VOS pool's own open/close/remove/replace lifecycle themselves, +so ddb does not pre-open a pool for them. When one of these is given as a single bare command +directly on the command line, `--vos_path`/`--db_path` are **not accepted**: ddb returns an error +and the path must be provided directly to the command instead (see its own `--help`). For example: + +```bash +# Rejected: rm_pool manages its own pool lifecycle and does not accept the top-level flags +# as a bare command. +ddb --db_path /path/to/sys/db --vos_path /path/to/vos-0 rm_pool + +# Works: provide rm_pool's own db_path flag and path argument directly. +ddb rm_pool --db_path /path/to/sys/db /path/to/vos-0 +``` + +In interactive mode or when running a `-f` command file, `--vos_path`/`--db_path` are accepted: they +only ever drive the one-time initial auto-open, and it is up to the user to close the pre-opened +pool (or target a different one) before running a pool-lifecycle command from within that session or +file that requires it to be closed. + ## `prov_mem` command ``` Prepare the memory environment for md-on-ssd mode Usage: - prov_mem [flags] db_path tmpfs_mount + prov_mem [flags] tmpfs_mount Args: - db_path string Path to the sys db. tmpfs_mount string Path to the tmpfs mountpoint. Flags: -h, --help display help + -p, --db_path string Path to the sys db. This command manages its own pool lifecycle and + does not accept the top-level --vos_path/--db_path as a bare command; + see "Pool-content commands vs. pool-lifecycle commands" above. -s, --tmpfs_size uint Specify tmpfs size(GiB) for mount. By default, the total size of all VOS files will be used. ``` @@ -170,10 +201,10 @@ This command is used when working with DAOS in md-on-ssd (metadata-on-SSD) mode. ```bash # Prepare memory environment with auto-calculated tmpfs size -ddb prov_mem /path/to/sys/db /mnt/tmpfs +ddb prov_mem --db_path /path/to/sys/db /mnt/tmpfs # Prepare memory environment with specific tmpfs size of 16 GiB -ddb prov_mem -s 16 /path/to/sys/db /mnt/tmpfs +ddb prov_mem --db_path /path/to/sys/db -s 16 /mnt/tmpfs ``` ### Notes @@ -181,3 +212,6 @@ ddb prov_mem -s 16 /path/to/sys/db /mnt/tmpfs - The `tmpfs_mount` path must not already be a mount point; otherwise, the command will fail with a "busy" error. - If `tmpfs_size` is not specified, the size will be automatically calculated based on the total size of all VOS files. - This command requires the system to be configured for MD-on-SSD mode. +- `db_path` used to be a positional argument (`ddb prov_mem `); it is now + a `-p`/`--db_path` flag, consistent with the other pool-lifecycle commands (`open`, `feature`, + `rm_pool`, `dev_list`, `dev_replace`, `smd_sync`).