Skip to content

Commit

Permalink
DAOS-10028 client: Add Go bindings for libdaos (Pool)
Browse files Browse the repository at this point in the history
Start the work of converting the raw cgo in the daos
tool into proper Go bindings for libdaos. This patch
covers pool functionality and adds some new infrastructure
common to both pools and containers.

Features: daos_cmd
Required-githooks: true
Signed-off-by: Michael MacDonald <[email protected]>
  • Loading branch information
mjmac committed Jan 12, 2025
1 parent 6dda9d7 commit 91c979d
Show file tree
Hide file tree
Showing 28 changed files with 4,026 additions and 613 deletions.
195 changes: 126 additions & 69 deletions src/control/cmd/daos/attribute.go
Original file line number Diff line number Diff line change
@@ -1,95 +1,162 @@
//
// (C) Copyright 2018-2021 Intel Corporation.
// (C) Copyright 2018-2024 Intel Corporation.
// (C) Copyright 2025 Google LLC
//
// SPDX-License-Identifier: BSD-2-Clause-Patent
//

package main

import (
"context"
"fmt"
"io"
"strings"
"unsafe"

"github.com/pkg/errors"

"github.com/daos-stack/daos/src/control/lib/txtfmt"
"github.com/daos-stack/daos/src/control/cmd/daos/pretty"
"github.com/daos-stack/daos/src/control/common/cmdutil"
"github.com/daos-stack/daos/src/control/lib/daos"
"github.com/daos-stack/daos/src/control/logging"
)

/*
#include "util.h"
*/
import "C"

type attrType int

const (
poolAttr attrType = iota
contAttr
)

func (at attrType) String() string {
switch at {
case poolAttr:
return "pool"
case contAttr:
return "container"
default:
return "unknown"
}
}

type (
attribute struct {
Name string `json:"name"`
Value []byte `json:"value,omitempty"`
attrCmd interface {
MustLogCtx() context.Context
cmdutil.JSONOutputter
logging.Logger
}

attrList []*attribute
attrListerGetter interface {
ListAttributes(context.Context) ([]string, error)
GetAttributes(context.Context, ...string) (daos.AttributeList, error)
}

attrSetter interface {
SetAttributes(context.Context, ...*daos.Attribute) error
}

attrDeleter interface {
DeleteAttributes(context.Context, ...string) error
}
)

func (al attrList) asMap() map[string][]byte {
m := make(map[string][]byte)
for _, a := range al {
m[a.Name] = a.Value
func listAttributes(cmd attrCmd, alg attrListerGetter, at attrType, id string, verbose bool) error {
var attrs daos.AttributeList
if !verbose {
attrNames, err := alg.ListAttributes(cmd.MustLogCtx())
if err != nil {
return errors.Wrapf(err, "failed to list attributes for %s %s", at, id)
}
attrs = attrListFromNames(attrNames)
} else {
var err error
attrs, err = alg.GetAttributes(cmd.MustLogCtx())
if err != nil {
return errors.Wrapf(err, "failed to get attributes for %s %s", at, id)
}
}
return m
}

func (al attrList) asList() []string {
names := make([]string, len(al))
for i, a := range al {
names[i] = a.Name
if cmd.JSONOutputEnabled() {
if verbose {
return cmd.OutputJSON(attrs.AsMap(), nil)
}
return cmd.OutputJSON(attrs.AsList(), nil)
}
return names

var bld strings.Builder
title := fmt.Sprintf("Attributes for %s %s:", at, id)
pretty.PrintAttributes(&bld, title, attrs...)

cmd.Info(bld.String())

return nil
}

func printAttributes(out io.Writer, header string, attrs ...*attribute) {
fmt.Fprintf(out, "%s\n", header)
func getAttributes(cmd attrCmd, alg attrListerGetter, at attrType, id string, names ...string) error {
attrs, err := alg.GetAttributes(cmd.MustLogCtx(), names...)
if err != nil {
return errors.Wrapf(err, "failed to get attributes for %s %s", at, id)
}

if len(attrs) == 0 {
fmt.Fprintln(out, " No attributes found.")
return
}

nameTitle := "Name"
valueTitle := "Value"
titles := []string{nameTitle}

table := []txtfmt.TableRow{}
for _, attr := range attrs {
row := txtfmt.TableRow{}
row[nameTitle] = attr.Name
if len(attr.Value) != 0 {
row[valueTitle] = string(attr.Value)
if len(titles) == 1 {
titles = append(titles, valueTitle)
}
if cmd.JSONOutputEnabled() {
// Maintain compatibility with older behavior.
if len(names) == 1 && len(attrs) == 1 {
return cmd.OutputJSON(attrs[0], nil)
}
table = append(table, row)
return cmd.OutputJSON(attrs, nil)
}

tf := txtfmt.NewTableFormatter(titles...)
tf.InitWriter(out)
tf.Format(table)
var bld strings.Builder
title := fmt.Sprintf("Attributes for %s %s:", at, id)
pretty.PrintAttributes(&bld, title, attrs...)

cmd.Info(bld.String())

return nil
}

type attrType int
func setAttributes(cmd attrCmd, as attrSetter, at attrType, id string, attrMap map[string]string) error {
if len(attrMap) == 0 {
return errors.New("attribute name and value are required")
}

const (
poolAttr attrType = iota
contAttr
)
attrs := make(daos.AttributeList, 0, len(attrMap))
for key, val := range attrMap {
attrs = append(attrs, &daos.Attribute{
Name: key,
Value: []byte(val),
})
}

func listDaosAttributes(hdl C.daos_handle_t, at attrType, verbose bool) (attrList, error) {
if err := as.SetAttributes(cmd.MustLogCtx(), attrs...); err != nil {
return errors.Wrapf(err, "failed to set attributes on %s %s", at, id)
}
cmd.Infof("Attributes successfully set on %s %q", at, id)

return nil
}

func delAttributes(cmd attrCmd, ad attrDeleter, at attrType, id string, names ...string) error {
attrsString := strings.Join(names, ",")
if err := ad.DeleteAttributes(cmd.MustLogCtx(), names...); err != nil {
return errors.Wrapf(err, "failed to delete attributes %s on %s %s", attrsString, at, id)
}
cmd.Infof("Attribute(s) %s successfully deleted on %s %q", attrsString, at, id)

return nil
}

// NB: These will be removed in the next patch, which adds the container APIs.
func listDaosAttributes(hdl C.daos_handle_t, at attrType, verbose bool) (daos.AttributeList, error) {
var rc C.int
expectedSize, totalSize := C.size_t(0), C.size_t(0)

switch at {
case poolAttr:
rc = C.daos_pool_list_attr(hdl, nil, &totalSize, nil)
case contAttr:
rc = C.daos_cont_list_attr(hdl, nil, &totalSize, nil)
default:
Expand All @@ -109,8 +176,6 @@ func listDaosAttributes(hdl C.daos_handle_t, at attrType, verbose bool) (attrLis
defer C.free(buf)

switch at {
case poolAttr:
rc = C.daos_pool_list_attr(hdl, (*C.char)(buf), &totalSize, nil)
case contAttr:
rc = C.daos_cont_list_attr(hdl, (*C.char)(buf), &totalSize, nil)
default:
Expand All @@ -130,9 +195,9 @@ func listDaosAttributes(hdl C.daos_handle_t, at attrType, verbose bool) (attrLis
return getDaosAttributes(hdl, at, attrNames)
}

attrs := make([]*attribute, len(attrNames))
attrs := make(daos.AttributeList, len(attrNames))
for i, name := range attrNames {
attrs[i] = &attribute{Name: name}
attrs[i] = &daos.Attribute{Name: name}
}

return attrs, nil
Expand All @@ -141,7 +206,7 @@ func listDaosAttributes(hdl C.daos_handle_t, at attrType, verbose bool) (attrLis

// getDaosAttributes fetches the values for the given list of attribute names.
// Uses the bulk attribute fetch API to minimize roundtrips.
func getDaosAttributes(hdl C.daos_handle_t, at attrType, names []string) (attrList, error) {
func getDaosAttributes(hdl C.daos_handle_t, at attrType, names []string) (daos.AttributeList, error) {
if len(names) == 0 {
attrList, err := listDaosAttributes(hdl, at, false)
if err != nil {
Expand Down Expand Up @@ -171,8 +236,6 @@ func getDaosAttributes(hdl C.daos_handle_t, at attrType, names []string) (attrLi
attrSizes := make([]C.size_t, numAttr)
var rc C.int
switch at {
case poolAttr:
rc = C.daos_pool_get_attr(hdl, C.int(numAttr), &attrNames[0], nil, &attrSizes[0], nil)
case contAttr:
rc = C.daos_cont_get_attr(hdl, C.int(numAttr), &attrNames[0], nil, &attrSizes[0], nil)
default:
Expand All @@ -199,8 +262,6 @@ func getDaosAttributes(hdl C.daos_handle_t, at attrType, names []string) (attrLi

// Do the actual fetch of all values in one go.
switch at {
case poolAttr:
rc = C.daos_pool_get_attr(hdl, C.int(numAttr), &attrNames[0], &attrValues[0], &attrSizes[0], nil)
case contAttr:
rc = C.daos_cont_get_attr(hdl, C.int(numAttr), &attrNames[0], &attrValues[0], &attrSizes[0], nil)
default:
Expand All @@ -214,9 +275,9 @@ func getDaosAttributes(hdl C.daos_handle_t, at attrType, names []string) (attrLi
// Note that we are copying the values into Go-managed byte slices
// for safety and simplicity so that we can free the C memory as soon
// as this function exits.
attrs := make([]*attribute, numAttr)
attrs := make(daos.AttributeList, numAttr)
for i, name := range names {
attrs[i] = &attribute{
attrs[i] = &daos.Attribute{
Name: name,
Value: C.GoBytes(attrValues[i], C.int(attrSizes[i])),
}
Expand All @@ -228,7 +289,7 @@ func getDaosAttributes(hdl C.daos_handle_t, at attrType, names []string) (attrLi
// getDaosAttribute fetches the value for the given attribute name.
// NB: For operations involving multiple attributes, the getDaosAttributes()
// function is preferred for efficiency.
func getDaosAttribute(hdl C.daos_handle_t, at attrType, name string) (*attribute, error) {
func getDaosAttribute(hdl C.daos_handle_t, at attrType, name string) (*daos.Attribute, error) {
attrs, err := getDaosAttributes(hdl, at, []string{name})
if err != nil {
return nil, err
Expand All @@ -241,7 +302,7 @@ func getDaosAttribute(hdl C.daos_handle_t, at attrType, name string) (*attribute

// setDaosAttributes sets the values for the given list of attribute names.
// Uses the bulk attribute set API to minimize roundtrips.
func setDaosAttributes(hdl C.daos_handle_t, at attrType, attrs attrList) error {
func setDaosAttributes(hdl C.daos_handle_t, at attrType, attrs daos.AttributeList) error {
if len(attrs) == 0 {
return nil
}
Expand Down Expand Up @@ -277,8 +338,6 @@ func setDaosAttributes(hdl C.daos_handle_t, at attrType, attrs attrList) error {
attrCount := C.int(len(attrs))
var rc C.int
switch at {
case poolAttr:
rc = C.daos_pool_set_attr(hdl, attrCount, &attrNames[0], &valBufs[0], &valSizes[0], nil)
case contAttr:
rc = C.daos_cont_set_attr(hdl, attrCount, &attrNames[0], &valBufs[0], &valSizes[0], nil)
default:
Expand All @@ -291,12 +350,12 @@ func setDaosAttributes(hdl C.daos_handle_t, at attrType, attrs attrList) error {
// setDaosAttribute sets the value for the given attribute name.
// NB: For operations involving multiple attributes, the setDaosAttributes()
// function is preferred for efficiency.
func setDaosAttribute(hdl C.daos_handle_t, at attrType, attr *attribute) error {
func setDaosAttribute(hdl C.daos_handle_t, at attrType, attr *daos.Attribute) error {
if attr == nil {
return errors.Errorf("nil %T", attr)
}

return setDaosAttributes(hdl, at, attrList{attr})
return setDaosAttributes(hdl, at, daos.AttributeList{attr})
}

func delDaosAttribute(hdl C.daos_handle_t, at attrType, name string) error {
Expand All @@ -305,8 +364,6 @@ func delDaosAttribute(hdl C.daos_handle_t, at attrType, name string) error {

var rc C.int
switch at {
case poolAttr:
rc = C.daos_pool_del_attr(hdl, 1, &attrName, nil)
case contAttr:
rc = C.daos_cont_del_attr(hdl, 1, &attrName, nil)
default:
Expand Down
Loading

0 comments on commit 91c979d

Please sign in to comment.