Skip to content

Commit

Permalink
DAOS-15982 pool: Enable pool list for clients
Browse files Browse the repository at this point in the history
Revive parts of the old client mgmt API to allow
libdaos clients to list pools in the system, but
only allow a given client to see the pools that
it could connect to.

Required-githooks: true
Change-Id: I2b3c391ddf042b23811be8f3390ec290e92e4290
Signed-off-by: Michael MacDonald <mjmac@google.com>
  • Loading branch information
mjmac committed Jun 15, 2024
1 parent 68b0d80 commit e7db73a
Show file tree
Hide file tree
Showing 36 changed files with 1,886 additions and 470 deletions.
2 changes: 1 addition & 1 deletion src/client/api/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const struct daos_task_api dc_funcs[] = {
/** Management */
{dc_deprecated, 0},
{dc_deprecated, 0},
{dc_deprecated, 0},
{dc_mgmt_pool_list, sizeof(daos_mgmt_pool_list_t)},
{dc_debug_set_params, sizeof(daos_set_params_t)},
{dc_mgmt_get_bs_state, sizeof(daos_mgmt_get_bs_state_t)},

Expand Down
26 changes: 26 additions & 0 deletions src/client/api/mgmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,29 @@ daos_mgmt_put_sys_info(struct daos_sys_info *info)
{
dc_mgmt_put_sys_info(info);
}

int
daos_mgmt_list_pools(const char *group, daos_size_t *npools, daos_mgmt_pool_info_t *pools,
daos_event_t *ev)
{
daos_mgmt_pool_list_t *args;
tse_task_t *task;
int rc;

DAOS_API_ARG_ASSERT(*args, MGMT_LIST_POOLS);

if (npools == NULL) {
D_ERROR("npools must be non-NULL\n");
return -DER_INVAL;
}

rc = dc_task_create(dc_mgmt_pool_list, NULL, ev, &task);
if (rc)
return rc;
args = dc_task_get_args(task);
args->grp = group;
args->pools = pools;
args->npools = npools;

return dc_task_schedule(task, true);
}
77 changes: 77 additions & 0 deletions src/control/cmd/daos/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/daos-stack/daos/src/control/lib/daos"
"github.com/daos-stack/daos/src/control/lib/ranklist"
"github.com/daos-stack/daos/src/control/lib/ui"
"github.com/daos-stack/daos/src/control/logging"
)

/*
Expand Down Expand Up @@ -180,6 +181,7 @@ func (cmd *poolBaseCmd) getAttr(name string) (*attribute, error) {
}

type poolCmd struct {
List poolListCmd `command:"list" description:"list pools to which this user has access"`
Query poolQueryCmd `command:"query" description:"query pool info"`
QueryTargets poolQueryTargetsCmd `command:"query-targets" description:"query pool target info"`
ListConts containerListCmd `command:"list-containers" alias:"list-cont" description:"list all containers in pool"`
Expand Down Expand Up @@ -601,3 +603,78 @@ func (cmd *poolAutoTestCmd) Execute(_ []string) error {

return nil
}

func getPoolList(log logging.Logger, queryEnabled bool) ([]*daos.PoolInfo, error) {
var rc C.int
bufSize := C.size_t(0)

// First, fetch the total number of pools in the system.
// We may not have access to all of them, so this is an upper bound.
rc = C.daos_mgmt_list_pools(nil, &bufSize, nil, nil)
if err := daosError(rc); err != nil {
return nil, err
}

if bufSize < 1 {
return nil, nil
}

// Now, we actually fetch the pools into the buffer that we've created.
cPools := make([]C.daos_mgmt_pool_info_t, bufSize)
rc = C.daos_mgmt_list_pools(nil, &bufSize, &cPools[0], nil)
if err := daosError(rc); err != nil {
return nil, err
}
pools := make([]*daos.PoolInfo, 0, bufSize)
for i := 0; i < int(bufSize); i++ {
poolUUID, err := uuidFromC(cPools[i].mgpi_uuid)
if err != nil {
return nil, err
}
svcRanks, err := rankSetFromC(cPools[i].mgpi_svc)
if err != nil {
return nil, err
}

// Populate the basic info.
pool := &daos.PoolInfo{
UUID: poolUUID,
Label: C.GoString(cPools[i].mgpi_label),
ServiceReplicas: svcRanks.Ranks(),
State: daos.PoolServiceStateReady,
}

if queryEnabled {
// TODO: rework query logic to be callable standalone.
}
pools = append(pools, pool)
}

log.Debugf("fetched %d/%d pools", len(pools), len(cPools))
return pools, nil
}

type poolListCmd struct {
daosCmd
Verbose bool `short:"v" long:"verbose" description:"Add pool UUIDs and service replica lists to display"`
NoQuery bool `short:"n" long:"no-query" description:"Disable query of listed pools"`
}

func (cmd *poolListCmd) Execute(_ []string) error {
pools, err := getPoolList(cmd.Logger, !cmd.NoQuery)
if err != nil {
return err
}

if cmd.JSONOutputEnabled() {
return cmd.OutputJSON(pools, nil)
}

var buf strings.Builder
if err := pretty.PrintPoolList(pools, &buf, cmd.Verbose); err != nil {
return err
}
cmd.Info(buf.String())

return nil
}
184 changes: 184 additions & 0 deletions src/control/cmd/daos/pretty/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,187 @@ func PrintPoolQueryTargetInfo(pqti *daos.PoolQueryTargetInfo, out io.Writer) err

return w.Err
}

func poolListCreateRow(pool *daos.PoolInfo, upgrade bool) txtfmt.TableRow {
// display size of the largest non-empty tier
var size uint64
poolUsage := pool.Usage()
for ti := len(poolUsage) - 1; ti >= 0; ti-- {
if poolUsage[ti].Size != 0 {
size = poolUsage[ti].Size
break
}
}

// display usage of the most used tier
var used int
for ti := 0; ti < len(poolUsage); ti++ {
t := poolUsage[ti]
u := float64(t.Size-t.Free) / float64(t.Size)

if int(u*100) > used {
used = int(u * 100)
}
}

// display imbalance of the most imbalanced tier
var imbalance uint32
for ti := 0; ti < len(poolUsage); ti++ {
if poolUsage[ti].Imbalance > imbalance {
imbalance = poolUsage[ti].Imbalance
}
}

row := txtfmt.TableRow{
"Pool": pool.Name(),
"Size": humanize.Bytes(size),
"State": pool.State.String(),
"Used": fmt.Sprintf("%d%%", used),
"Imbalance": fmt.Sprintf("%d%%", imbalance),
"Disabled": fmt.Sprintf("%d/%d", pool.DisabledTargets, pool.TotalTargets),
}

if upgrade {
upgradeString := "None"

if pool.PoolLayoutVer != pool.UpgradeLayoutVer {
upgradeString = fmt.Sprintf("%d->%d", pool.PoolLayoutVer, pool.UpgradeLayoutVer)
}
row["UpgradeNeeded?"] = upgradeString
}

return row
}

func printPoolList(pools []*daos.PoolInfo, out io.Writer) error {
upgrade := false
for _, pool := range pools {
if pool.PoolLayoutVer != pool.UpgradeLayoutVer {
upgrade = true
}
}

titles := []string{"Pool", "Size", "State", "Used", "Imbalance", "Disabled"}
if upgrade {
titles = append(titles, "UpgradeNeeded?")
}
formatter := txtfmt.NewTableFormatter(titles...)

var table []txtfmt.TableRow
for _, pool := range pools {
table = append(table, poolListCreateRow(pool, upgrade))
}

fmt.Fprintln(out, formatter.Format(table))

return nil
}

func addVerboseTierUsage(row txtfmt.TableRow, usage *daos.PoolTierUsage) txtfmt.TableRow {
row[usage.TierName+" Size"] = humanize.Bytes(usage.Size)
row[usage.TierName+" Used"] = humanize.Bytes(usage.Size - usage.Free)
row[usage.TierName+" Imbalance"] = fmt.Sprintf("%d%%", usage.Imbalance)

return row
}

func poolListCreateRowVerbose(pool *daos.PoolInfo, hasSpace, hasRebuild bool) txtfmt.TableRow {
label := pool.Label
if label == "" {
label = "-"
}

svcReps := "N/A"
if len(pool.ServiceReplicas) != 0 {
svcReps = PrintRanks(pool.ServiceReplicas)
}

upgrade := "None"
if pool.PoolLayoutVer != pool.UpgradeLayoutVer {
upgrade = fmt.Sprintf("%d->%d", pool.PoolLayoutVer, pool.UpgradeLayoutVer)
}

row := txtfmt.TableRow{
"Label": label,
"UUID": pool.UUID.String(),
"State": pool.State.String(),
"SvcReps": svcReps,
}
if hasSpace {
row["Disabled"] = fmt.Sprintf("%d/%d", pool.DisabledTargets, pool.TotalTargets)
row["UpgradeNeeded?"] = upgrade
}
if hasRebuild {
row["Rebuild State"] = pool.RebuildState()
}

if hasSpace {
for _, tu := range pool.Usage() {
row = addVerboseTierUsage(row, tu)
}
}

return row
}

func printVerbosePoolList(pools []*daos.PoolInfo, out io.Writer) error {
// Basic pool info should be available without a query.
titles := []string{"Label", "UUID", "State", "SvcReps"}

hasSpaceQuery := false
hasRebuildQuery := false
for _, pool := range pools {
if hasSpaceQuery && hasRebuildQuery {
break
}

if pool.QueryMask.HasOption(daos.PoolQueryOptionSpace) {
hasSpaceQuery = true
}
if pool.QueryMask.HasOption(daos.PoolQueryOptionRebuild) {
hasRebuildQuery = true
}
}

// If any of the pools was queried, then we'll need to show more fields.
if hasSpaceQuery {
for _, t := range pools[0].Usage() {
titles = append(titles,
t.TierName+" Size",
t.TierName+" Used",
t.TierName+" Imbalance")
}
titles = append(titles, "Disabled")
titles = append(titles, "UpgradeNeeded?")
}

if hasRebuildQuery {
titles = append(titles, "Rebuild State")
}

formatter := txtfmt.NewTableFormatter(titles...)

var table []txtfmt.TableRow
for _, pool := range pools {
table = append(table, poolListCreateRowVerbose(pool, hasSpaceQuery, hasRebuildQuery))
}

fmt.Fprintln(out, formatter.Format(table))

return nil
}

// PrintPoolList generates a human-readable representation of the supplied
// slice of daos.PoolInfo structs and writes it to the supplied io.Writer.
func PrintPoolList(pools []*daos.PoolInfo, out io.Writer, verbose bool) error {
if len(pools) == 0 {
fmt.Fprintln(out, "no pools in system")
return nil
}

if verbose {
return printVerbosePoolList(pools, out)
}

return printPoolList(pools, out)
}
Loading

0 comments on commit e7db73a

Please sign in to comment.