Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DAOS-15794 tools: Add --health-only flag to (dmg|daos) pool query #14414

Merged
merged 4 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 70 additions & 70 deletions src/control/cmd/daos/pool.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// (C) Copyright 2021-2023 Intel Corporation.
// (C) Copyright 2021-2024 Intel Corporation.
//
// SPDX-License-Identifier: BSD-2-Clause-Patent
//
Expand All @@ -15,11 +15,9 @@ import (
"github.com/google/uuid"
"github.com/pkg/errors"

"github.com/daos-stack/daos/src/control/cmd/dmg/pretty"
"github.com/daos-stack/daos/src/control/cmd/daos/pretty"
"github.com/daos-stack/daos/src/control/common"
"github.com/daos-stack/daos/src/control/common/proto/convert"
mgmtpb "github.com/daos-stack/daos/src/control/common/proto/mgmt"
"github.com/daos-stack/daos/src/control/lib/control"
"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"
)
Expand Down Expand Up @@ -196,83 +194,74 @@ type poolQueryCmd struct {
poolBaseCmd
ShowEnabledRanks bool `short:"e" long:"show-enabled" description:"Show engine unique identifiers (ranks) which are enabled"`
ShowDisabledRanks bool `short:"b" long:"show-disabled" description:"Show engine unique identifiers (ranks) which are disabled"`
HealthOnly bool `short:"t" long:"health-only" description:"Only perform pool health related queries"`
}

func convertPoolSpaceInfo(in *C.struct_daos_pool_space, mt C.uint) *mgmtpb.StorageUsageStats {
func convertPoolSpaceInfo(in *C.struct_daos_pool_space, mt C.uint) *daos.StorageUsageStats {
if in == nil {
return nil
}

return &mgmtpb.StorageUsageStats{
return &daos.StorageUsageStats{
Total: uint64(in.ps_space.s_total[mt]),
Free: uint64(in.ps_space.s_free[mt]),
Min: uint64(in.ps_free_min[mt]),
Max: uint64(in.ps_free_max[mt]),
Mean: uint64(in.ps_free_mean[mt]),
MediaType: mgmtpb.StorageMediaType(mt),
MediaType: daos.StorageMediaType(mt),
}
}

func convertPoolRebuildStatus(in *C.struct_daos_rebuild_status) *mgmtpb.PoolRebuildStatus {
func convertPoolRebuildStatus(in *C.struct_daos_rebuild_status) *daos.PoolRebuildStatus {
if in == nil {
return nil
}

out := &mgmtpb.PoolRebuildStatus{
out := &daos.PoolRebuildStatus{
Status: int32(in.rs_errno),
}
if out.Status == 0 {
out.Objects = uint64(in.rs_obj_nr)
out.Records = uint64(in.rs_rec_nr)
switch {
case in.rs_version == 0:
out.State = mgmtpb.PoolRebuildStatus_IDLE
out.State = daos.PoolRebuildStateIdle
case C.get_rebuild_state(in) == C.DRS_COMPLETED:
out.State = mgmtpb.PoolRebuildStatus_DONE
out.State = daos.PoolRebuildStateDone
default:
out.State = mgmtpb.PoolRebuildStatus_BUSY
out.State = daos.PoolRebuildStateBusy
}
}

return out
}

// This is not great... But it allows us to leverage the existing
// pretty printer that dmg uses for this info. Better to find some
// way to unify all of this and remove redundancy/manual conversion.
//
// We're basically doing the same thing as ds_mgmt_drpc_pool_query()
// to stuff the info into a protobuf message and then using the
// automatic conversion from proto to control. Kind of ugly but
// gets the job done. We could potentially create some function
// that's shared between this code and the drpc handlers to deal
// with stuffing the protobuf message but it's probably overkill.
func convertPoolInfo(pinfo *C.daos_pool_info_t) (*control.PoolQueryResp, error) {
pqp := new(mgmtpb.PoolQueryResp)

pqp.Uuid = uuid.Must(uuidFromC(pinfo.pi_uuid)).String()
pqp.TotalTargets = uint32(pinfo.pi_ntargets)
pqp.DisabledTargets = uint32(pinfo.pi_ndisabled)
pqp.ActiveTargets = uint32(pinfo.pi_space.ps_ntargets)
pqp.TotalEngines = uint32(pinfo.pi_nnodes)
pqp.Leader = uint32(pinfo.pi_leader)
pqp.Version = uint32(pinfo.pi_map_ver)

pqp.TierStats = []*mgmtpb.StorageUsageStats{
convertPoolSpaceInfo(&pinfo.pi_space, C.DAOS_MEDIA_SCM),
convertPoolSpaceInfo(&pinfo.pi_space, C.DAOS_MEDIA_NVME),
}
pqp.Rebuild = convertPoolRebuildStatus(&pinfo.pi_rebuild_st)

pqr := new(control.PoolQueryResp)
return pqr, convert.Types(pqp, pqr)
}

const (
dpiQuerySpace = C.DPI_SPACE
dpiQueryRebuild = C.DPI_REBUILD_STATUS
dpiQueryAll = C.uint64_t(^uint64(0)) // DPI_ALL is -1
)
func convertPoolInfo(pinfo *C.daos_pool_info_t) (*daos.PoolInfo, error) {
poolInfo := new(daos.PoolInfo)

poolInfo.QueryMask = daos.PoolQueryMask(pinfo.pi_bits)
poolInfo.UUID = uuid.Must(uuidFromC(pinfo.pi_uuid))
poolInfo.TotalTargets = uint32(pinfo.pi_ntargets)
poolInfo.DisabledTargets = uint32(pinfo.pi_ndisabled)
poolInfo.ActiveTargets = uint32(pinfo.pi_space.ps_ntargets)
poolInfo.TotalEngines = uint32(pinfo.pi_nnodes)
poolInfo.ServiceLeader = uint32(pinfo.pi_leader)
poolInfo.Version = uint32(pinfo.pi_map_ver)
poolInfo.State = daos.PoolServiceStateReady
if poolInfo.DisabledTargets > 0 {
poolInfo.State = daos.PoolServiceStateDegraded
}

poolInfo.Rebuild = convertPoolRebuildStatus(&pinfo.pi_rebuild_st)
if poolInfo.QueryMask.HasOption(daos.PoolQueryOptionSpace) {
poolInfo.TierStats = []*daos.StorageUsageStats{
convertPoolSpaceInfo(&pinfo.pi_space, C.DAOS_MEDIA_SCM),
convertPoolSpaceInfo(&pinfo.pi_space, C.DAOS_MEDIA_NVME),
}
}

return poolInfo, nil
}

func generateRankSet(ranklist *C.d_rank_list_t) string {
if ranklist.rl_nr == 0 {
Expand All @@ -292,9 +281,20 @@ func generateRankSet(ranklist *C.d_rank_list_t) string {
}

func (cmd *poolQueryCmd) Execute(_ []string) error {
queryMask := daos.DefaultPoolQueryMask
if cmd.HealthOnly {
queryMask = daos.HealthOnlyPoolQueryMask
}
if cmd.ShowEnabledRanks && cmd.ShowDisabledRanks {
return errors.New("show-enabled and show-disabled can't be used at the same time.")
}
if cmd.ShowEnabledRanks {
queryMask.SetOptions(daos.PoolQueryOptionEnabledEngines)
}
if cmd.ShowDisabledRanks {
queryMask.SetOptions(daos.PoolQueryOptionDisabledEngines)
}

var rlPtr **C.d_rank_list_t = nil
var rl *C.d_rank_list_t = nil

Expand All @@ -308,43 +308,41 @@ func (cmd *poolQueryCmd) Execute(_ []string) error {
}
defer cleanup()

pinfo := C.daos_pool_info_t{
pi_bits: dpiQueryAll,
}
if cmd.ShowDisabledRanks {
pinfo.pi_bits &= C.uint64_t(^(uint64(C.DPI_ENGINES_ENABLED)))
cPoolInfo := C.daos_pool_info_t{
pi_bits: C.uint64_t(queryMask),
}

rc := C.daos_pool_query(cmd.cPoolHandle, rlPtr, &pinfo, nil, nil)
rc := C.daos_pool_query(cmd.cPoolHandle, rlPtr, &cPoolInfo, nil, nil)
defer C.d_rank_list_free(rl)
if err := daosError(rc); err != nil {
return errors.Wrapf(err,
"failed to query pool %s", cmd.poolUUID)
}

pqr, err := convertPoolInfo(&pinfo)
poolInfo, err := convertPoolInfo(&cPoolInfo)
if err != nil {
return err
}

if rlPtr != nil {
if cmd.ShowEnabledRanks {
pqr.EnabledRanks = ranklist.MustCreateRankSet(generateRankSet(rl))
poolInfo.EnabledRanks = ranklist.MustCreateRankSet(generateRankSet(rl))
}
if cmd.ShowDisabledRanks {
pqr.DisabledRanks = ranklist.MustCreateRankSet(generateRankSet(rl))
poolInfo.DisabledRanks = ranklist.MustCreateRankSet(generateRankSet(rl))
}
}

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

var bld strings.Builder
if err := pretty.PrintPoolQueryResponse(pqr, &bld); err != nil {
if err := pretty.PrintPoolInfo(poolInfo, &bld); err != nil {
return err
}

cmd.Debugf("Pool query options: %s", poolInfo.QueryMask)
cmd.Info(bld.String())

return nil
Expand All @@ -358,11 +356,11 @@ type poolQueryTargetsCmd struct {
}

// For using the pretty printer that dmg uses for this target info.
func convertPoolTargetInfo(ptinfo *C.daos_target_info_t) (*control.PoolQueryTargetInfo, error) {
pqti := new(control.PoolQueryTargetInfo)
pqti.Type = control.PoolQueryTargetType(ptinfo.ta_type)
pqti.State = control.PoolQueryTargetState(ptinfo.ta_state)
pqti.Space = []*control.StorageTargetUsage{
func convertPoolTargetInfo(ptinfo *C.daos_target_info_t) (*daos.PoolQueryTargetInfo, error) {
pqti := new(daos.PoolQueryTargetInfo)
pqti.Type = daos.PoolQueryTargetType(ptinfo.ta_type)
pqti.State = daos.PoolQueryTargetState(ptinfo.ta_state)
pqti.Space = []*daos.StorageUsageStats{
{
Total: uint64(ptinfo.ta_space.s_total[C.DAOS_MEDIA_SCM]),
Free: uint64(ptinfo.ta_space.s_free[C.DAOS_MEDIA_SCM]),
Expand Down Expand Up @@ -390,10 +388,10 @@ func (cmd *poolQueryTargetsCmd) Execute(_ []string) error {
return errors.WithMessage(err, "parsing target list")
}

infoResp := new(control.PoolQueryTargetResp)
ptInfo := new(C.daos_target_info_t)
var rc C.int

infos := make([]*daos.PoolQueryTargetInfo, 0, len(idxList))
for tgt := 0; tgt < len(idxList); tgt++ {
rc = C.daos_pool_query_target(cmd.cPoolHandle, C.uint32_t(idxList[tgt]), C.uint32_t(cmd.Rank), ptInfo, nil)
if err := daosError(rc); err != nil {
Expand All @@ -402,19 +400,21 @@ func (cmd *poolQueryTargetsCmd) Execute(_ []string) error {
}

tgtInfo, err := convertPoolTargetInfo(ptInfo)
infoResp.Infos = append(infoResp.Infos, tgtInfo)
if err != nil {
return err
}
infos = append(infos, tgtInfo)
}

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

var bld strings.Builder
if err := pretty.PrintPoolQueryTargetResponse(infoResp, &bld); err != nil {
return err
for _, info := range infos {
if err := pretty.PrintPoolQueryTargetInfo(info, &bld); err != nil {
return err
}
}

cmd.Info(bld.String())
Expand Down
100 changes: 100 additions & 0 deletions src/control/cmd/daos/pretty/pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// (C) Copyright 2020-2024 Intel Corporation.
//
// SPDX-License-Identifier: BSD-2-Clause-Patent
//

package pretty

import (
"fmt"
"io"

"github.com/dustin/go-humanize"
"github.com/pkg/errors"

"github.com/daos-stack/daos/src/control/lib/daos"
"github.com/daos-stack/daos/src/control/lib/txtfmt"
)

const msgNoPools = "No pools in system"

func getTierNameText(tierIdx int) string {
switch tierIdx {
case int(daos.StorageMediaTypeScm):
return fmt.Sprintf("- Storage tier %d (SCM):", tierIdx)
case int(daos.StorageMediaTypeNvme):
return fmt.Sprintf("- Storage tier %d (NVMe):", tierIdx)
default:
return fmt.Sprintf("- Storage tier %d (unknown):", tierIdx)
}
}

// PrintPoolInfo generates a human-readable representation of the supplied
// PoolQueryResp struct and writes it to the supplied io.Writer.
func PrintPoolInfo(pi *daos.PoolInfo, out io.Writer) error {
if pi == nil {
return errors.Errorf("nil %T", pi)
}
w := txtfmt.NewErrWriter(out)

// Maintain output compatibility with the `daos pool query` output.
fmt.Fprintf(w, "Pool %s, ntarget=%d, disabled=%d, leader=%d, version=%d, state=%s\n",
pi.UUID, pi.TotalTargets, pi.DisabledTargets, pi.ServiceLeader, pi.Version, pi.State)

if pi.PoolLayoutVer != pi.UpgradeLayoutVer {
fmt.Fprintf(w, "Pool layout out of date (%d < %d) -- see `dmg pool upgrade` for details.\n",
pi.PoolLayoutVer, pi.UpgradeLayoutVer)
}
fmt.Fprintln(w, "Pool health info:")
if pi.EnabledRanks != nil && pi.EnabledRanks.Count() > 0 {
fmt.Fprintf(w, "- Enabled ranks: %s\n", pi.EnabledRanks)
}
if pi.DisabledRanks != nil && pi.DisabledRanks.Count() > 0 {
fmt.Fprintf(w, "- Disabled ranks: %s\n", pi.DisabledRanks)
}
if pi.Rebuild != nil {
if pi.Rebuild.Status == 0 {
fmt.Fprintf(w, "- Rebuild %s, %d objs, %d recs\n",
pi.Rebuild.State, pi.Rebuild.Objects, pi.Rebuild.Records)
} else {
fmt.Fprintf(w, "- Rebuild failed, status=%d\n", pi.Rebuild.Status)
}
} else {
fmt.Fprintln(w, "- No rebuild status available.")
}

if pi.QueryMask.HasOption(daos.PoolQueryOptionSpace) && pi.TierStats != nil {
fmt.Fprintln(w, "Pool space info:")
fmt.Fprintf(w, "- Target(VOS) count:%d\n", pi.ActiveTargets)
for tierIdx, tierStats := range pi.TierStats {
fmt.Fprintln(w, getTierNameText(tierIdx))
fmt.Fprintf(w, " Total size: %s\n", humanize.Bytes(tierStats.Total))
fmt.Fprintf(w, " Free: %s, min:%s, max:%s, mean:%s\n",
humanize.Bytes(tierStats.Free), humanize.Bytes(tierStats.Min),
humanize.Bytes(tierStats.Max), humanize.Bytes(tierStats.Mean))
}
}
return w.Err
}

// PrintPoolQueryTargetInfo generates a human-readable representation of the supplied
// PoolQueryTargetResp struct and writes it to the supplied io.Writer.
func PrintPoolQueryTargetInfo(pqti *daos.PoolQueryTargetInfo, out io.Writer) error {
if pqti == nil {
return errors.Errorf("nil %T", pqti)
}
w := txtfmt.NewErrWriter(out)

// Maintain output compatibility with the `daos pool query-targets` output.
fmt.Fprintf(w, "Target: type %s, state %s\n", pqti.Type, pqti.State)
if pqti.Space != nil {
for tierIdx, tierUsage := range pqti.Space {
fmt.Fprintln(w, getTierNameText(tierIdx))
fmt.Fprintf(w, " Total size: %s\n", humanize.Bytes(tierUsage.Total))
fmt.Fprintf(w, " Free: %s\n", humanize.Bytes(tierUsage.Free))
}
}

return w.Err
}
Loading
Loading